'
' *  Authors:  Benton Stark
' * 
' *  Copyright (c) 2007-2012 Starksoft, LLC (http://www.starksoft.com) 
' *
' * Permission is hereby granted, free of charge, to any person obtaining a copy
' * of this software and associated documentation files (the "Software"), to deal
' * in the Software without restriction, including without limitation the rights
' * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
' * copies of the Software, and to permit persons to whom the Software is
' * furnished to do so, subject to the following conditions:
' * 
' * The above copyright notice and this permission notice shall be included in
' * all copies or substantial portions of the Software.
' * 
' * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
' * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
' * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
' * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
' * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
' * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
' * THE SOFTWARE.
' * 
' 


Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Text
Imports System.Threading
Imports System.Globalization
Imports System.ComponentModel
Imports SFDL.FTP.FTP

Namespace FTP

#Region "Public Enums"
    ''' <summary>
    ''' Enumeration representing type of file transfer mode.
    ''' </summary>
    Public Enum TransferType As Integer
        ''' <summary>
        ''' No transfer type.
        ''' </summary>
        None
        ''' <summary>
        ''' Transfer mode of type 'A' (ascii).
        ''' </summary>
        Ascii
        ''' <summary>
        ''' Transfer mode of type 'I' (image or binary)
        ''' </summary>
        Binary
        ''' <summary>
        ''' Unknown transfer mode.
        ''' </summary>
        Unknown
    End Enum

    ''' <summary>
    ''' Enumeration representing the three types of actions that FTP supports when
    ''' uploading or 'putting' a file on an FTP server from the FTP client.  
    ''' </summary>
    Public Enum FileAction As Integer
        ''' <summary>
        ''' No action.
        ''' </summary>
        None
        ''' <summary>
        ''' Create a new file or overwrite an existing file.
        ''' </summary>
        Create
        ''' <summary>
        ''' Create a new file.  Do not overwrite an existing file.
        ''' </summary>
        CreateNew
        ''' <summary>
        ''' Create a new file or append an existing file.
        ''' </summary>
        CreateOrAppend
        ''' <summary>
        ''' Resume a file transfer.
        ''' </summary>
        [Resume]
        ''' <summary>
        ''' Resume a file transfer if the file already exists.  Otherwise, create a new file.
        ''' </summary>
        ResumeOrCreate
    End Enum

    ''' <summary>
    ''' FTP command and response encoding.
    ''' </summary>
    Public Enum CharEncoding As Integer
        ''' <summary>
        ''' Use ASCII encoding to send commands and receive responses.
        ''' </summary>
        Ascii
        ''' <summary>
        ''' Use UTF-8 encoding to send commands and receive responses.  
        ''' </summary>
        Utf8
    End Enum

    ''' <summary>
    ''' Enumeration representing the type of file and directory listing method to use and commands to issue
    ''' to the FTP server to retrieve file and directory information.
    ''' </summary>
    Public Enum ListingMethod As Integer
        ''' <summary>
        ''' Always use the standard LIST command with no additional argument information when retrieving a directory listing from the FTP server.  
        ''' </summary>
        List
        ''' <summary>
        ''' Always use the standard LIST command with the additional argument '-al' when retrieving a directory listing from the FTP server.  
        ''' </summary>
        ListAl
        ''' <summary>
        ''' Always use the RFC 3659 MLSx commands when retrieving a directory listing from the FTP server.  
        ''' </summary>
        Mlsx
        ''' <summary>
        ''' Let the Starksoft FtpClient figure out which option to use when retrieve a directory listing from the FTP server.  
        ''' </summary>
        ''' <remarks>
        ''' The Starksoft FtpClient will first attempt to use the MLSx command, if that command is not available it will automatically downgrade to the
        ''' LIST 'al' command.  
        ''' </remarks>
        Automatic
    End Enum

#End Region

    ''' <summary>
    ''' The Startsoft FtpClient library for .NET is a fully .NET coded RFC 959 compatible FTP object component that supports the RFC 959, SOCKS and HTTP proxies, SSLv2, SSLv3, and TLSv1
    ''' (explicit and implicit) as well as automatic file integrity checks on all data transfers.  The component library also supports a pluggable directory listing parser that can be extended 
    ''' by the developer to parse unique directory formats returned by odd or old FTP servers.  Most methods are available to call in a standard blocking pattern or the non-blocking .NET 2.0 
    ''' Async pattern which features a callback event handler that return the result on background thread.
    ''' </summary>
    ''' <remarks>
    ''' <para>
    ''' The FtpClient supports following FTP commands: 
    '''     USER    RMD     CDUP    CWD     STOU    RETR    AUTH    XSHA512  CLNT
    '''     PASS    RETR    DELE    PORT    APPE    MDTM    PROT    OPTS     HASH
    '''     QUIT    PWD     TYPE    PASV    REST    SIZE    MODE    SITE     RANG
    '''     MKD     SYST    MODE    STOR    RNFR    FEAT    XSHA1   CHMOD    MLST
    '''     NLST    HELP    RNTO    SITE    ALLO    QUIT    XMD5    MFMT     MLSD
    '''     ABORT   STAT    LIST    NOOP    PBSZ    XCRC    XSHA256 MFCT
    ''' </para>
    ''' <para>
    ''' Custom FTP server commands can be executed using the Quote() method.  This allows the FtpClient object to handle
    ''' certain custom commands that are not supported by the RFC 959 standard but are required by specific FTP server
    ''' implementations for various tasks.  
    ''' </para>
    ''' <code>
    ''' string result = "";
    ''' using (FtpClient ftp = new FtpClient("ftp.gnu.org"))
    ''' {
    '''     result = ftp.Quote("SITE ZONE"); 
    ''' } // using end bracket closes connection
    ''' </code>
    ''' <para>
    ''' The Starksoft FtpClient Component for .NET supports SOCKS v4, SOCKS v4a, SOCKS v5, and HTTP proxy servers.  The proxy settings are not read
    ''' from the local web browser proxy settings so deployment issues are not a problem with using proxy connections.  In addition the library also
    ''' supports active and passive (firewall friendly) mode transfers.  The Starksoft FtpClient Component for .NET supports data compression, bandwidth throttling,
    ''' and secure connections through SSL (Secure Socket Layer) and TLS.  The Starksoft FtpClient Component for .NET also supports automatic transfer integrity checks via 
    ''' CRC, MD5, SHA1, SHA-256, and SHA-512.  The FtpClient object can parse many different directory listings from various FTP server implementations.  But for those servers that are difficult to 
    ''' parse of produce strange directory listings you can write your own ftp item parser.  See the IFtpItemParser interface
    ''' for more information and an example parser.     
    ''' </para>
    ''' <para>
    ''' The FtpClient libary has been tested with the following FTP servers and file formats.
    ''' <list type="">
    '''     <item>FileZilla</item>
    '''     <item>IIS Microsoft Windows 2000 and Windows 2003 server</item>
    '''     <item>Microsoft FTP server running IIS 5.0</item>
    '''     <item>Gene6FTP Server</item>
    '''     <item>ProFTPd</item>
    '''     <item>Wu-FTPd</item>
    '''     <item>WS_FTP Server (by Ipswitch)</item>
    '''     <item>Serv-U FTP Server</item>
    '''     <item>GNU FTP server</item>
    '''     <item>Many public FTP servers</item>
    ''' </list>
    ''' </para>
    ''' </remarks>
    ''' <example>
    ''' <code>
    ''' using (FtpClient ftp = new FtpClient("ftp.gnu.org"))
    ''' {
    '''     ftp.DataTransferMode = DataTransferMode.Passive; 
    '''     ftp.Open("anonymous", "myemail@host.com");
    '''     ftp.GetFile("somefile", "c:\\somefile"); 
    ''' } // using end bracket closes connection
    ''' </code>
    ''' </example>
    Public Class FtpClient
        Inherits FtpBase
#Region "Contructors"

        ''' <summary>
        ''' FtpClient default constructor.
        ''' </summary>
        Public Sub New()
            MyBase.New(DEFAULT_FTP_PORT, FtpSecurityProtocol.None)
        End Sub

        ''' <summary>
        ''' Constructor method for FtpClient.  
        ''' </summary>
        ''' <param name="host">String containing the host name or ip address of the remote FTP server.</param>
        ''' <remarks>
        ''' This method takes one parameter to specify
        ''' the host name (or ip address).
        ''' </remarks>
        Public Sub New(host As String)
            Me.New(host, DEFAULT_FTP_PORT, FtpSecurityProtocol.None)
        End Sub

        ''' <summary>
        ''' Constructor method for FtpClient.  
        ''' </summary>
        ''' <param name="host">String containing the host name or ip address of the remote FTP server.</param>
        ''' <param name="port">Port number used to connect to the remote FTP server.</param>
        ''' <remarks>
        ''' This method takes two parameters that specify 
        ''' the host name (or ip address) and the port to connect to the host.
        ''' </remarks>
        Public Sub New(host As String, port As Integer)
            MyBase.New(host, port, FtpSecurityProtocol.None)
        End Sub

        ''' <summary>
        ''' Constructor method for FtpClient.  
        ''' </summary>
        ''' <param name="host">String containing the host name or ip address of the remote FTP server.</param>
        ''' <param name="port">Port number used to connect to the remote FTP server.</param>
        ''' <param name="securityProtocol">Enumeration value indicating what security protocol (such as SSL) should be enabled for this connection.</param>
        ''' <remarks>
        ''' This method takes three parameters that specify 
        ''' the host name (or ip address), port to connect to and what security protocol should be used when establishing the connection.
        ''' </remarks>
        Public Sub New(host As String, port As Integer, securityProtocol As FtpSecurityProtocol)
            MyBase.New(host, port, securityProtocol)
        End Sub

#End Region

#Region "Private Variables and Constants"

        Private Const FTP_CLIENT_NAME As String = "SFDL.NET"
        Private Const DEFAULT_FTP_PORT As Integer = 21
        ' default port is 21
        Private Const FXP_TRANSFER_TIMEOUT As Integer = 600000
        ' 10 minutes
        Private Const TYPE_BINARY_I As String = "I"
        Private Const TYPE_ASCII_A As String = "A"

        ' file and directory listing variables
        Private _fileTransferType As TransferType = TransferType.Binary
        Private _dirListingMethod As ListingMethod = ListingMethod.Automatic
        Private _listItemParser As IFtpItemParser
        Private _mlsxItemParser As IFtpItemParser
        Private _currentDirectory As String
        Private _verifyfeatureset As Boolean = True

        Private _user As String
        Private _password As String
        Private _opened As Boolean
        Private _fxpTransferTimeout As Integer = FXP_TRANSFER_TIMEOUT

        ' transfer log
        Private _log As Stream = New MemoryStream()
        Private _isLoggingOn As Boolean

#End Region

#Region "Public Properties"


        'Public ReadOnly Property CharacterEncoding() As CharEncoding
        '    Get
        '        'If MyBase.Encoding = System.Text.Encoding.ASCII Then
        '        '    Return CharEncoding.Ascii
        '        'ElseIf MyBase.Encoding = Encoding.UTF8 Then
        '        '    Return CharEncoding.Utf8
        '        'Else
        '        '    Throw New FtpException("unexpected encoding type")
        '        'End If
        '        Return CharEncoding.Utf8
        '    End Get
        'End Property

        Public Property CharacterEncoding As System.Text.Encoding
            Get
                Return MyBase.Encoding
            End Get

            Set(value As System.Text.Encoding)
                MyBase.Encoding = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the file transfer item.
        ''' </summary>
        Public Property FileTransferType() As TransferType
            Get
                Return _fileTransferType
            End Get
            Set(value As TransferType)
                If Me.IsBusy Then
                    Throw New FtpBusyException("FileTransferType")
                End If
                If Me.IsConnected = True Then
                    '  update the server with the new file transfer type
                    SetFileTransferType(value)
                End If
                _fileTransferType = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the the directory item parser to use when parsing directory listing data from the FTP server.
        ''' This parser is used by the GetDirList() and GetDirList(string) methods.  
        ''' </summary>
        ''' <remarks>
        ''' You can create your own custom directory listing parser by creating an object that implements the 
        ''' IFtpItemParser interface.  This is particular useful when parsing exotic file directory listing
        ''' formats from specific FTP servers.
        ''' 
        ''' This parser is only used when the LIST command is executed.  You can indicate the type of command
        ''' behavior by setting the DirListingMethod property.  This ItemParser may be used if the DirListingMethod
        ''' property is set to List, ListAl, or Automatic.
        ''' </remarks>
        Public Property ItemParser() As IFtpItemParser
            Get
                Return _listItemParser
            End Get
            Set(value As IFtpItemParser)
                If Me.IsBusy Then
                    Throw New FtpBusyException("ItemParser")
                End If

                _listItemParser = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets logging of file transfers.
        ''' </summary>
        ''' <remarks>
        ''' All data transfer activity can be retrieved from the Log property.
        ''' </remarks>
        Public Property IsLoggingOn() As Boolean
            Get
                Return _isLoggingOn
            End Get
            Set(value As Boolean)
                If Me.IsBusy Then
                    Throw New FtpBusyException("IsLoggingOn")
                End If
                _isLoggingOn = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the Stream object used for logging data transfer activity.
        ''' </summary>
        ''' <remarks>
        ''' By default a MemoryStream object is created to log all data transfer activity.  Any 
        ''' Stream object that can be written to may be used in place of the MemoryStream object.
        ''' </remarks>
        ''' <seealso cref="IsLoggingOn"/>
        Public Property Log() As Stream
            Get
                Return _log
            End Get
            Set(value As Stream)
                If Me.IsBusy Then
                    Throw New FtpBusyException("Log")
                End If
                If DirectCast(value, Stream).CanWrite = False Then
                    Throw New ArgumentException("must be writable. The property CanWrite must have a value equals to 'true'.", "value")
                End If
                _log = value
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets the timeout value in miliseconds when waiting for an FXP server to server transfer to complete.
        ''' </summary>
        ''' <remarks>By default this timeout value is set to 600000 (10 minutes).  For large FXP file transfers you may need to adjust this number higher.</remarks>
        Public Property FxpTransferTimeout() As Integer
            Get
                Return _fxpTransferTimeout
            End Get
            Set(value As Integer)
                If Me.IsBusy Then
                    Throw New FtpBusyException("FxpTransferTimeout")
                End If
                _fxpTransferTimeout = value
            End Set
        End Property

        ''' <summary>
        ''' Gets the current directory path without sending having to send a request to the server.
        ''' </summary>
        ''' <seealso cref="GetWorkingDirectory"/>
        Public ReadOnly Property CurrentDirectory() As String
            Get
                Return _currentDirectory
            End Get
        End Property

        ''' <summary>
        ''' Gets or sets the method how directory and file listing commands will be executed on the FTP server.
        ''' </summary>
        ''' <remarks>
        ''' The enmeration options for the listing method affects how the methods GetDirList(), GetDirListAsText(), GetDirListAsync(), GetDirListDeep(),
        ''' and GetDirListDeepAsync() function.  Different options will result in different commands being executed on the FTP server in order to option
        ''' directory listing information.  The most widely flexible option is the List option which will executed the LIST command on the FTP server 
        ''' and option unstructured or semistructured file and directory information.  The Starksoft FtpClient will then parse that data as and present
        ''' a FtpListCollection that contains FtpListItem objects.  
        ''' 
        ''' Some FTP servers will not return detailed listing data with the UNIX file permissions
        ''' either because they do not support them or those FTP servers require an argument to be specified with the LIST command.  That argument is 
        ''' the value '-al' which is a UNIX specific argument to instruct the server to list all the information about the files and not simply the file
        ''' names.  The problem is that not all FTP servers will accept this argument.  To Use the optional argument choose the enumeration option ListAll.
        ''' 
        ''' More modern FTP servers implement the a much more structured method to retrieve file and directory listings from a FTP server.  Two new commands 
        ''' were defined in RFC 3659.  Those new commands are MLST and MLSD or MLSx for short.  The MLSx commands instruct the FTP server to send much more
        ''' structured data about the files and directories located on the FTP server.  This helps reduce or eliminate the errors that are common when 
        ''' parsing different type types of directory listing supported by various FTP servers.  Unfortunately, many FTP serves do not support the MLSx
        ''' commands.  
        ''' 
        ''' You can specify that this type of command should be used when retrieving a directory listing from a specific FTP server by
        ''' selecting the enumeration option Mlsx.  This option will instruct the Starksoft FtpClient to use the MLSx commands instead of the LIST command
        ''' to retrive directory listing information.  The standard FtpItemCollection will be returned but it will now include objects of class type
        ''' FtpMlsxItem which is a sub-class of the FtpListItem.  The FtpMlsxItem class contains additional information that may be available and supported
        ''' by your FTP server.  Yet, not all FTP servers that support MLSx commands support all the file and directory facts defined by RFC 3659.  Each
        ''' FTP server will support a different range of facts.  In order to support these differences, the FtpMlsxItem class has many nullable data types.
        ''' </remarks>
        ''' <seealso cref="ListingMethod"/>
        Public Property DirListingMethod() As ListingMethod
            Get
                Return _dirListingMethod
            End Get
            Set(value As ListingMethod)
                If MyBase.IsBusy Then
                    Throw New FtpBusyException("DirListingMethod")
                End If
                _dirListingMethod = value
            End Set
        End Property

        Public ReadOnly Property GetAvailableFeatures As FtpFeatureCollection
            Get
                Return MyBase.Features
            End Get
        End Property


        Public Property VerifyFeatureSet As Boolean
            Get
                Return _verifyfeatureset
            End Get
            Set(value As Boolean)
                _verifyfeatureset = value
            End Set
        End Property

#End Region

#Region "Public Methods"

        ''' <summary>
        ''' Opens a connection to the remote FTP server and log in with user name and password credentials.
        ''' </summary>
        ''' <param name="user">User name.  Many public ftp allow for this value to 'anonymous'.</param>
        ''' <param name="password">Password.  Anonymous public ftp servers generally require a valid email address for a password.</param>
        ''' <remarks>Use the Close() method to log off and close the connection to the FTP server.</remarks>
        ''' <seealso cref="OpenAsync"/>
        ''' <seealso cref="Close"/>
        ''' <seealso cref="Reopen"/>
        Public Sub Open(user As String, password As String)
            If user Is Nothing Then
                Throw New ArgumentNullException("user", "must have a value")
            End If

            If user.Length = 0 Then
                Throw New ArgumentException("must have a value", "user")
            End If

            If password Is Nothing Then
                Throw New ArgumentNullException("password", "must have a value or an empty string")
            End If

            ' if the command connection is not already open then open a new command connect
            If Not Me.IsConnected Then
                MyBase.OpenCommandConn()
            End If

            ' create the LIST and MLSx item parsers
            CreateItemParsers()

            If CatchUserCancel() Then
                Return
            End If

            _user = user
            _password = password
            _currentDirectory = "/"

            SendUser(user)

            ' wait for user to log into system and all response messages to be transmitted
            Thread.Sleep(500)

            If CatchUserCancel() Then
                Return
            End If

            SendPassword(password)

            If CatchUserCancel() Then
                Return
            End If

            TrySetFeatures()
            TrySetClient()
            'TrySetUtf8On()
            TrySetFileTransferType()
            SetCompression()

            If CatchUserCancel() Then
                Return
            End If

            ' mark the connection as opened
            _opened = True
        End Sub

        ''' <summary>
        ''' Reopens a lost ftp connection.
        ''' </summary>
        ''' <remarks>
        ''' If the connection is currently open or the connection has never been open and FtpException is thrown.
        ''' </remarks>
        Public Sub Reopen()
            If Not _opened Then
                Throw New FtpException("You must use the Open() method before using the Reopen() method.")
            End If

            ' reopen the connection with the same username and password
            Open(_user, _password)
        End Sub

        ''' <summary>
        ''' Change the currently logged in user to another user on the FTP server.
        ''' </summary>
        ''' <param name="user">The name of user.</param>
        ''' <param name="password">The password for the user.</param>
        Public Sub ChangeUser(user As String, password As String)
            If user Is Nothing Then
                Throw New ArgumentNullException("user", "must have a value")
            End If

            If user.Length = 0 Then
                Throw New ArgumentException("must have a value", "user")
            End If

            If password Is Nothing Then
                Throw New ArgumentNullException("password", "must have a value")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.User, user))
            Catch fex As FtpException
                Throw New FtpException("An error occurred when sending user information.", MyBase.LastResponse, fex)
            End Try

            ' wait for user to log into system and all response messages to be transmitted
            Thread.Sleep(500)

            ' test to see if this is an asychronous operation and if so make sure 
            ' the user has not requested the operation to be canceled
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.CancellationPending Then
                MyBase.CloseAllConnections()
                Return
            End If

            ' some ftp servers do not require passwords for users and will log you in immediately - no password command is required
            If MyBase.LastResponse.Code <> FtpResponseCode.UserLoggedIn Then
                Try
                    MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Pass, password))
                Catch fex As FtpException
                    Throw New FtpException("An error occurred when sending password information.", MyBase.LastResponse, fex)
                End Try

                If MyBase.LastResponse.Code = FtpResponseCode.NotLoggedIn Then
                    Throw New FtpLoginException("Unable to log into FTP destination with supplied username and password.")
                End If
            End If
        End Sub

        ''' <summary>
        ''' Closes connection to the FTP server.
        ''' </summary>
        ''' <seealso cref="FtpBase.ConnectionClosed"/>
        ''' <seealso cref="Reopen"/>
        ''' <seealso cref="Open"/>
        Public Sub Close()
            MyBase.CloseAllConnections()
        End Sub

        ''' <summary>
        ''' Changes the current working directory on older FTP servers that cannot handle a full path containing
        ''' multiple subdirectories.  This method will separate the full path into separate change directory commands
        ''' to support such systems.
        ''' </summary>
        ''' <param name="path">Path of the new directory to change to.</param>
        ''' <remarks>Accepts both foward slash '/' and back slash '\' path names.</remarks>
        ''' <seealso cref="ChangeDirectory"/>
        ''' <seealso cref="GetWorkingDirectory"/>
        Public Sub ChangeDirectoryMultiPath(path As String)
            ' the change working dir command can generally handle all the weird directory name spaces
            ' which is nice but frustrating that the ftp server implementors did not fix it for other commands

            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            If path.Length = 0 Then
                Throw New ArgumentException("must have a value", "path")
            End If

            ' replace the windows style directory delimiter with a unix style delimiter
            path = path.Replace("\", "/")

            Dim dirs As String() = path.Split(New Char() {"/"c}, StringSplitOptions.RemoveEmptyEntries)

            Try
                ' issue a single CWD command for each directory
                ' this is a very reliable method to change directories on all FTP servers
                ' because some systems do not all a full path to be specified when changing directories
                For Each dir As String In dirs
                    MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Cwd, dir))
                Next
            Catch fex As FtpException
                Throw New FtpException([String].Format("Could not change working directory to '{0}'.", path), fex)
            End Try

            _currentDirectory = GetWorkingDirectory()
        End Sub

        ''' <summary>
        ''' Changes the current working directory on the server.  Some FTP server will not accept this command 
        ''' if the path contains mutiple directories.  For those FTP server implementations see the method
        ''' ChangeDirectoryMultiPath(string).
        ''' </summary>
        ''' <param name="path">Path of the new directory to change to.</param>
        ''' <remarks>Accepts both foward slash '/' and back slash '\' path names.</remarks>
        Public Sub ChangeDirectory(path As String)
            ' the change working dir command can generally handle all the weird directory name spaces
            ' which is nice but frustrating that the ftp server implementors did not fix it for other commands
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must have a value", "path")
            End If
            ' replace the windows style directory delimiter with a unix style delimiter
            path = path.Replace("\", "/")

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Cwd, path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("Could not change working directory to '{0}'.", path), fex)
            End Try

            _currentDirectory = GetWorkingDirectory()
        End Sub


        ''' <summary>
        ''' Gets the current working directory.
        ''' </summary>
        ''' <return>A string value containing the current full working directory path on the FTP server.</return>
        ''' <seealso cref="ChangeDirectory"/>
        ''' <seealso cref="ChangeDirectoryUp"/>
        Public Function GetWorkingDirectory() As String
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Pwd))
            Catch fex As FtpException
                Throw New FtpException("Could not retrieve working directory.", MyBase.LastResponse, fex)
            End Try

            '  now we have to fix the directory due to formatting
            '  most ftp servers send something like this:  257 "/awg/inbound" is current directory.
            Dim dir As String = MyBase.LastResponse.Text

            '  if the pwd is in quotes, then extract it
            If dir.Substring(0, 1) = """" Then
                dir = dir.Substring(1, dir.IndexOf("""", 1) - 1)
            End If

            Return dir
        End Function

        ''' <summary>
        ''' Deletes a file on the remote FTP server.  
        ''' </summary>
        ''' <param name="path">The path name of the file to delete.</param>
        ''' <remarks>
        ''' The file is deleted in the current working directory if no path information 
        ''' is specified.  Otherwise a full absolute path name can be specified.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to delete the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="DeleteDirectory"/>
        Public Sub DeleteFile(path As String)
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must have a value", "path")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Dele, path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("Unable to the delete file {0}.", path), MyBase.LastResponse, fex)
            End Try
        End Sub

        ''' <summary>
        ''' Aborts an action such as transferring a file to or from the server.  
        ''' </summary>
        ''' <remarks>
        ''' The abort command is sent up to the server signaling the server to abort the current activity.
        ''' </remarks>
        Public Sub Abort()
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Abor))
            Catch fex As FtpException
                Throw New FtpException("Abort command failed or was unable to be issued.", MyBase.LastResponse, fex)
            End Try
        End Sub

        ''' <summary>
        ''' Creates a new directory on the remote FTP server.  
        ''' </summary>
        ''' <param name="path">The name of a new directory or an absolute path name for a new directory.</param>
        ''' <remarks>
        ''' If a directory name is given for path then the server will create a new subdirectory 
        ''' in the current working directory.  Optionally, a full absolute path may be given.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to make the subdirectory using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        Public Sub MakeDirectory(path As String)
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must contain a value", "path")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Mkd, path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("The directory {0} could not be created.", path), MyBase.LastResponse, fex)
            End Try
        End Sub

        ''' <summary>
        ''' Moves a file on the remote FTP server from one directory to another.  
        ''' </summary>
        ''' <param name="fromPath">Path and/or file name to be moved.</param>
        ''' <param name="toPath">Destination path specifying the directory where the file will be moved to.</param>
        ''' <remarks>
        ''' This method actually results in several FTP commands being issued to the server to perform the physical file move.  
        ''' This method is available for your convenience when performing common tasks such as moving processed files out of a pickup directory
        ''' and into a archive directory.
        ''' Note that some older FTP server implementations will not accept a full path to a filename.  On those systems this method may not work
        ''' properly.
        ''' </remarks>
        Public Sub MoveFile(fromPath As String, toPath As String)
            If fromPath Is Nothing Then
                Throw New ArgumentNullException("fromPath")
            End If
            If fromPath.Length = 0 Then
                Throw New ArgumentException("must contain a value", "fromPath")
            End If
            If toPath Is Nothing Then
                Throw New ArgumentNullException("toPath")
            End If
            If fromPath.Length = 0 Then
                Throw New ArgumentException("must contain a value", "toPath")
            End If

            '  retrieve the server file from the current working directory
            Dim ms As New MemoryStream()
            GetFile(fromPath, ms, False)

            '  create the remote file in the new location
            ms.Position = 0
            ' reset stream position to zero
            Me.PutFile(ms, toPath, FileAction.Create)

            '  delete the original file from the original location
            Me.DeleteFile(fromPath)
        End Sub

        ''' <summary>
        ''' Deletes a directory from the FTP server.
        ''' </summary>
        ''' <param name="path">Directory to delete.</param>
        ''' <remarks>
        ''' The path can be either a specific subdirectory relative to the 
        ''' current working directory on the server or an absolute path to 
        ''' the directory to remove.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the parent directory of the directory you wish to delete using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="DeleteFile"/>
        Public Sub DeleteDirectory(path As String)
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must have a value", "path")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Rmd, path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("The FTP destination was unable to delete the directory '{0}'.", path), MyBase.LastResponse, fex)
            End Try

        End Sub
        ''' <summary>
        ''' Executes the specific help dialog on the FTP server.  
        ''' </summary>
        ''' <return>
        ''' A string contains the help dialog from the FTP server.
        ''' </return>
        ''' <remarks>
        ''' Every FTP server supports a different set of commands and this commands 
        ''' can be obtained by the FTP HELP command sent to the FTP server.  The information sent
        ''' back is not parsed or processed in any way by the FtpClient object.  
        ''' </remarks>
        Public Function GetHelp() As String
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Help))
            Catch fex As FtpException
                Throw New FtpException("An error occurred while getting the system help.", MyBase.LastResponse, fex)
            End Try

            Return MyBase.LastResponse.Text
        End Function

        ''' <summary>
        ''' Retrieves the data and time for a specific file on the ftp server as a Coordinated Universal Time (UTC) value (formerly known as GMT). 
        ''' </summary>
        ''' <param name="fileName">The name of the file.</param>
        ''' <param name="adjustToLocalTime">Specifies if modified date and time as reported on the FTP server should be adjusted to the local time zone with daylight savings on the client.</param>
        ''' <return>
        ''' A date time value.
        ''' </return>
        ''' <remarks>
        ''' This function uses the MDTM command which is an additional feature command and therefore not supported
        ''' by all FTP servers.
        ''' </remarks>
        ''' <seealso cref="GetFileSize"/>
        Public Function GetFileDateTime(fileName As String, adjustToLocalTime As Boolean) As DateTime
            If [String].IsNullOrEmpty(fileName) Then
                Throw New ArgumentException("must contain a value", "fileName")
            End If
            If Not MyBase.Features.Contains(FtpCmd.Mdtm) Then
                Throw New FtpCommandNotSupportedException("Cannot get the file date and time information.", FtpCmd.Mdtm)
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Mdtm, fileName))
            Catch fex As FtpException
                Throw New FtpException([String].Format("An error occurred when retrieving file date and time for '{0}'.", fileName), MyBase.LastResponse, fex)
            End Try

            Dim response As String = MyBase.LastResponse.Text

            Dim year As Integer = Integer.Parse(response.Substring(0, 4), CultureInfo.InvariantCulture)
            Dim month As Integer = Integer.Parse(response.Substring(4, 2), CultureInfo.InvariantCulture)
            Dim day As Integer = Integer.Parse(response.Substring(6, 2), CultureInfo.InvariantCulture)
            Dim hour As Integer = Integer.Parse(response.Substring(8, 2), CultureInfo.InvariantCulture)
            Dim minute As Integer = Integer.Parse(response.Substring(10, 2), CultureInfo.InvariantCulture)
            Dim second As Integer = Integer.Parse(response.Substring(12, 2), CultureInfo.InvariantCulture)

            Dim dateUtc As New DateTime(year, month, day, hour, minute, second, _
                DateTimeKind.Utc)

            If adjustToLocalTime Then
                Return New DateTime(dateUtc.ToLocalTime().Ticks)
            Else
                Return New DateTime(dateUtc.Ticks)
            End If
        End Function

        ''' <summary>
        ''' Set the modified date and time value for a specific file.  Not all FTP servers support this command.  In addition
        ''' some FTP servers do not support a qualified or relative path value.
        ''' </summary>
        ''' <param name="path">Filename or fully qualified or partial path.  Note that note all FTP server support a qualified or partial path</param>
        ''' <param name="dateTime">New modified date and time value.</param>
        ''' <exception cref="FtpCommandNotSupportedException"></exception>
        Public Sub SetModifiedDateTime(path As String, dateTime As DateTime)
            SetDateTime(path, dateTime, FtpCmd.Mfmt)
        End Sub

        ''' <summary>
        ''' Set the created date and time value for a specific file.  Not all FTP servers support this command.  In addition
        ''' some FTP servers do not support a qualified or relative path value.
        ''' </summary>
        ''' <param name="path">Filename or fully qualified or partial path.  Note that note all FTP server support a qualified or partial path</param>
        ''' <param name="dateTime">New created date and time value.</param>
        ''' <exception cref="FtpCommandNotSupportedException"></exception>
        Public Sub SetCreatedDateTime(path As String, dateTime As DateTime)
            SetDateTime(path, dateTime, FtpCmd.Mfct)
        End Sub


        ''' <summary>
        ''' Retrieves the specific status for the FTP server.  
        ''' </summary>
        ''' <remarks>
        ''' Each FTP server may return different status dialog information.  The status information sent
        ''' back is not parsed or processed in any way by the FtpClient object. 
        ''' </remarks>
        ''' <return>
        ''' A string containing the status of the FTP server.
        ''' </return>
        Public Function GetStatus() As String
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Stat))
            Catch fex As FtpException
                Throw New FtpException("An error occurred while getting the system status.", MyBase.LastResponse, fex)
            End Try

            Return MyBase.LastResponse.Text
        End Function

        ''' <summary>
        ''' Changes the current working directory on the FTP server to the parent directory.  
        ''' </summary>
        ''' <remarks>
        ''' If there is no parent directory then ChangeDirectoryUp() will not have 
        ''' any affect on the current working directory.
        ''' </remarks>
        ''' <seealso cref="ChangeDirectory"/>
        ''' <seealso cref="GetWorkingDirectory"/>
        Public Sub ChangeDirectoryUp()
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Cdup))
            Catch fex As FtpException
                Throw New FtpException("An error occurred when changing directory to the parent (ChangeDirectoryUp).", MyBase.LastResponse, fex)
            End Try
            _currentDirectory = GetWorkingDirectory()
        End Sub

        ''' <summary>
        ''' Get the file size for a file on the remote FTP server.  
        ''' </summary>
        ''' <param name="path">The name and/or path to the file.</param>
        ''' <param name="size">Size of the file.</param>
        ''' <return>An integer specifying the file size; otherwise -1</return>
        ''' <remarks>
        ''' This method will attempt to acquire the size of the file using server commands in the following order.
        ''' (1) Use the SIZE command if it is a supported feature.
        ''' (2) Use the MLST command if it is a supported feature and the DirListingMethod is set to Mlsx or Automatic.
        ''' (3) Use the LIST al command if DirListingMethod is set to ListAl.
        ''' (4) Use the LIST command with no optional parameters.
        ''' 
        ''' The path can be file name relative to the current working directory or an absolute path.  This command is an additional feature 
        ''' that is not supported by all FTP servers.
        ''' 
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to get the file size using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        Public Function TryGetFileSize(path As String, ByRef size As Long) As Boolean
            size = -1
            Try
                size = GetFileSize(path)
            Catch
                Return False
            End Try
            Return True
        End Function

        ''' <summary>
        ''' Get the file size for a file on the remote FTP server.  
        ''' </summary>
        ''' <param name="path">The name and/or path to the file.</param>
        ''' <return>An integer specifying the file size.</return>
        ''' <remarks>
        ''' This method will attempt to acquire the size of the file using server commands in the following order.
        ''' (1) Use the SIZE command if it is a supported feature.
        ''' (2) Use the MLST command if it is a supported feature and the DirListingMethod is set to Mlsx or Automatic.
        ''' (3) Use the LIST al command if DirListingMethod is set to ListAl.
        ''' (4) Use the LIST command with no optional parameters.
        ''' 
        ''' The path can be file name relative to the current working directory or an absolute path.  This command is an additional feature 
        ''' that is not supported by all FTP servers.
        ''' 
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to get the file size using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="GetFileDateTime"/>
        Public Function GetFileSize(path As String) As Long

            Dim _last_response As String = String.Empty

            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must contain a value", "path")
            End If

            Dim size As Long = 0

            ' test to see if SIZE is a supported feature
            If MyBase.Features.Contains(FtpCmd.Size) Then
                Try
                    ' if the SIZE command is supported always use it 
                    MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Size, path))
                Catch fex As FtpException
                    Throw New FtpException([String].Format("An error occurred while attempting to get size of file '{0}' using the SIZE command.", path), MyBase.LastResponse, fex)
                End Try

                If MyBase.LastResponse.Text.Contains("bytes") Then
                    _last_response = MyBase.LastResponse.Text.Replace("bytes", "").Trim
                Else
                    _last_response = MyBase.LastResponse.Text
                End If

                size = Int64.Parse(_last_response, CultureInfo.InvariantCulture)

            Else
                ' SIZE command not supported
                Dim item As FtpItem = Nothing
                Try
                    ' attempt to get the file size by executing the method GetFileInfo()
                    ' which will go through a decision tree to determine which method
                    ' is best to get the file size data
                    item = GetFileInfo(path)
                Catch fex As FtpException
                    Throw New FtpException([String].Format("An error occurred while attempting to get size of file {0} using the GetFileInfo() method.", path), MyBase.LastResponse, fex)
                End Try
                size = item.Size
            End If

            Return size
        End Function




        ''' <summary>
        ''' Retrieves the specific status for a file on the FTP server.  
        ''' </summary>
        ''' <param name="path">
        ''' The path to the file.
        ''' </param>
        ''' <return>
        ''' A string containing the status for the file.
        ''' </return>
        ''' <remarks>
        ''' Each FTP server may return different status dialog information.  The status information sent
        ''' back is not parsed or processed in any way by the FtpClient object. 
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to get the status of the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        Public Function GetStatus(path As String) As String
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must contain a value", "path")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Stat, path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("An error occurred when retrieving file status for file '{0}'.", path), MyBase.LastResponse, fex)
            End Try

            Return MyBase.LastResponse.Text
        End Function

        ''' <summary>
        ''' Allocates storage for a file on the FTP server prior to data transfer from the FTP client.  
        ''' </summary>
        ''' <param name="size">
        ''' The storage size to allocate on the FTP server.
        ''' </param>
        ''' <remarks>
        ''' Some FTP servers may return the client to specify the storage size prior to data transfer from the FTP client to the FTP server.
        ''' </remarks>
        Public Sub AllocateStorage(size As Long)
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Allo, size.ToString()))
            Catch fex As FtpException
                Throw New FtpException("An error occurred when trying to allocate storage on the destination.", MyBase.LastResponse, fex)
            End Try
        End Sub

        ''' <summary>
        ''' Retrieves a string identifying the remote FTP system.  
        ''' </summary>
        ''' <return>
        ''' A string contains the server type.
        ''' </return>
        ''' <remarks>
        ''' The string contains the word "Type:", and the default transfer type 
        ''' For example a UNIX FTP server will return 'UNIX Type: L8'.  A Windows 
        ''' FTP server will return 'WINDOWS_NT'.
        ''' </remarks>
        Public Function GetSystemType() As String
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Syst))
            Catch fex As FtpException
                Throw New FtpException("An error occurred while getting the system type.", MyBase.LastResponse, fex)
            End Try

            Return MyBase.LastResponse.Text
        End Function

        ''' <summary>
        ''' Uploads a local file specified in the path parameter to the remote FTP server.  
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server.  
        ''' A unique file name is created by the server.    
        ''' </remarks>
        ''' <return>
        ''' Name of the unique file created on the FTP server is a 36 character hex GUID value which may not
        ''' be valid for use on all FTP servers.
        ''' </return>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>  
        Public Function PutFileUnique(localPath As String) As String
            If localPath Is Nothing Then
                Throw New ArgumentNullException("localPath")
            End If

            Dim fname As String = ""
            Try
                Using fileStream As FileStream = File.OpenRead(localPath)
                    fname = PutFileUnique(fileStream)
                End Using
            Catch fex As FtpException
                WriteToLog([String].Format("Action='PutFileUnique';Action='TransferError';LocalPath='{0}';CurrentDirectory='{1}';ErrorMessage='{2}'", localPath, _currentDirectory, fex.Message))
                Throw New FtpException("An error occurred while executing PutFileUnique() on the remote FTP destination.", MyBase.LastResponse, fex)
            End Try

            Return fname
        End Function

        ''' <summary>
        ''' Uploads any stream object to the remote FTP server and stores the data under a unique file name assigned by the FTP server
        ''' into the current working directory.
        ''' </summary>
        ''' <param name="inputStream">Any stream object on the local client machine.</param>
        ''' <remarks>
        ''' The stream is uploaded to the current working directory on the remote server.  
        ''' A unique file name is created by the server to store the data uploaded from the stream.
        ''' </remarks>
        ''' <return>
        ''' Name of the unique file created on the FTP server is a 36 character hex GUID value which may not
        ''' be valid for use on all FTP servers.
        ''' </return>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>  
        Public Function PutFileUnique(inputStream As Stream) As String
            If inputStream Is Nothing Then
                Throw New ArgumentNullException("inputStream")
            End If
            If Not inputStream.CanRead Then
                Throw New ArgumentException("must be readable.  The CanRead property must return a value of 'true'.", "inputStream")
            End If

            Dim fname As String = Guid.NewGuid().ToString()
            WriteToLog([String].Format("Action='PutFileUnique';Action='TransferBegin';CurrentDirectory='{0}';FileName='{1}", _currentDirectory, fname))

            Try
                MyBase.TransferData(TransferDirection.ToServer, New FtpRequest(MyBase.Encoding, FtpCmd.Stor, fname), inputStream)
            Catch ex As Exception
                WriteToLog([String].Format("Action='PutFileUnique';Action='TransferError';CurrentDirectory='{0}';FileName='{1};ErrorMessage='{1}'", _currentDirectory, fname, ex.Message))
                Throw New FtpException("An error occurred while executing PutFileUnique() on the remote FTP destination.", MyBase.LastResponse, ex)
            End Try

            WriteToLog([String].Format("Action='PutFileUnique';Action='TransferSuccess';CurrentDirectory='{0}';FileName='{1}'", _currentDirectory, fname))

            Return fname
        End Function

        ''' <summary>
        ''' Retrieves a remote file from the FTP server and writes the data to a local file
        ''' specfied in the localPath parameter.  If the local file already exists a System.IO.IOException is thrown.
        ''' </summary>
        ''' <remarks>
        ''' To retrieve a remote file that you need to overwrite an existing file with or append to an existing file
        ''' see the method GetFile(string, string, FileAction).
        ''' </remarks>
        ''' <param name="remotePath">A path of the remote file.</param>
        ''' <param name="localPath">A fully qualified local path to a file on the local machine.</param>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>
        Public Sub GetFile(remotePath As String, localPath As String)
            GetFile(remotePath, localPath, FileAction.CreateNew)
        End Sub

        ''' <summary>
        ''' Retrieves a remote file from the FTP server and writes the data to a local file
        ''' specfied in the localPath parameter.
        ''' </summary>
        ''' <remarks>
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to get the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <param name="remotePath">A path and/or file name to the remote file.</param>
        ''' <param name="localPath">A fully qualified local path to a file on the local machine.</param>
        ''' <param name="action">The type of action to take.</param>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>
        Public Sub GetFile(remotePath As String, localPath As String, action As FileAction)
            If remotePath Is Nothing Then
                Throw New ArgumentNullException("remotePath")
            End If
            If remotePath.Length = 0 Then
                Throw New ArgumentException("must contain a value", "remotePath")
            End If
            If localPath Is Nothing Then
                Throw New ArgumentNullException("localPath")
            End If
            If localPath.Length = 0 Then
                Throw New ArgumentException("must contain a value", "localPath")
            End If
            If action = FileAction.None Then
                Throw New ArgumentOutOfRangeException("action", "must contain a value other than 'Unknown'")
            End If

            ' if the transfer event is being subscribed or this is a resume action then get the file size
            Dim remoteSize As Long = -1
            If IsTranferProgressEventSet() OrElse action = FileAction.[Resume] Then
                ' try to get the remote file size 
                TryGetFileSize(remotePath, remoteSize)
                ' set the transfer size so we can do some calc on percentage completed
                ' in the TransferBytes() method of the base class
                SetTransferSize(remoteSize)
            End If

            localPath = CorrectLocalPath(localPath)

            WriteToLog([String].Format("Action='GetFile';Status='TransferBegin';LocalPath='{0}';RemotePath='{1}';FileAction='{2}'", localPath, remotePath, action.ToString()))

            Dim request As New FtpRequest(MyBase.Encoding, FtpCmd.Retr, remotePath)

            Try
                Select Case action
                    Case FileAction.CreateNew
                        ' create a file stream to stream the file locally to disk that only creates the file if it does not already exist
                        Using localFile As Stream = File.Open(localPath, FileMode.CreateNew)
                            TransferData(TransferDirection.ToClient, request, localFile)
                        End Using
                        Exit Select

                    Case FileAction.Create
                        ' create a file stream to stream the file locally to disk
                        Using localFile As Stream = File.Open(localPath, FileMode.Create)
                            TransferData(TransferDirection.ToClient, request, localFile)
                        End Using
                        Exit Select
                    Case FileAction.CreateOrAppend
                        ' open the local file
                        Using localFile As Stream = File.Open(localPath, FileMode.OpenOrCreate)
                            ' set the file position to the end so that any new data will be appended                        
                            localFile.Position = localFile.Length
                            TransferData(TransferDirection.ToClient, request, localFile)
                        End Using
                        Exit Select
                    Case FileAction.[Resume]

                        If _verifyfeatureset = True Then
                            If Not MyBase.Features.Contains(FtpCmd.Rest, "STREAM") Then
                                Throw New FtpCommandNotSupportedException("Cannot resume file transfer.", "REST STREAM")
                            End If
                        End If

                        Using localFile As Stream = File.Open(localPath, FileMode.Open)
                            '  make sure we have a valid file size
                            If remoteSize = -1 Then
                                Throw New FtpException("unable to determine file size for resume transfer")
                            End If

                            ' if the files are the same size then there is nothing to transfer
                            If localFile.Length = remoteSize Then
                                Return
                            End If

                            TransferData(TransferDirection.ToClient, request, localFile, localFile.Length - 1)
                        End Using
                        Exit Select

                    Case FileAction.ResumeOrCreate
                        If File.Exists(localPath) AndAlso (New FileInfo(localPath)).Length > 0 Then
                            GetFile(remotePath, localPath, FileAction.[Resume])
                        Else
                            GetFile(remotePath, localPath, FileAction.Create)
                        End If
                        Exit Select
                End Select
            Catch ex As Exception
                WriteToLog([String].Format("Action='GetFile';Status='TransferError';LocalPath='{0}';RemotePath='{1}';FileAction='{1}';ErrorMessage='{2}", localPath, remotePath, action.ToString(), ex.Message))
                Throw New FtpException([String].Format("An unexpected exception occurred while retrieving file '{0}'.", remotePath), MyBase.LastResponse, ex)
            End Try

            WriteToLog([String].Format("Action='GetFile';Status='TransferSuccess';LocalPath='{0}';RemotePath='{1}';FileAction='{1}'", localPath, remotePath, action.ToString()))
        End Sub

        ''' <summary>
        ''' Retrieves a remote file from the FTP server and writes the data to a local stream object
        ''' specfied in the outStream parameter.
        ''' </summary> 
        ''' <param name="remotePath">A path and/or file name to the remote file.</param>
        ''' <param name="outStream">An output stream object used to stream the remote file to the local machine.</param>
        ''' <param name="resume">A true/false value to indicate if the file download needs to be restarted due to a previous partial download.</param>
        ''' <remarks>
        ''' If the remote path is a file name then the file is downloaded from the FTP server current working directory.  Otherwise a fully qualified
        ''' path for the remote file may be specified.  The output stream must be writeable and can be any stream object.  Finally, the restart parameter
        ''' is used to send a restart command to the FTP server.  The FTP server is instructed to restart the download process at the last position of
        ''' of the output stream.  Not all FTP servers support the restart command.  If the FTP server does not support the restart (REST) command,
        ''' an FtpException error is thrown.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to get the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>
        Public Sub GetFile(remotePath As String, outStream As Stream, [resume] As Boolean)
            If remotePath Is Nothing Then
                Throw New ArgumentNullException("remotePath")
            End If
            If remotePath.Length = 0 Then
                Throw New ArgumentException("must contain a value", "remotePath")
            End If
            If outStream Is Nothing Then
                Throw New ArgumentNullException("outStream")
            End If
            If outStream.CanWrite = False Then
                Throw New ArgumentException("must be writable.  The CanWrite property must return the value 'true'.", "outStream")
            End If

            ' if the transfer event is being subscribed or this is a resume action then get the file size
            Dim remoteSize As Long = -1
            If IsTranferProgressEventSet() OrElse [resume] Then
                ' try to get the remote file size 
                TryGetFileSize(remotePath, remoteSize)
                ' set the transfer size so we can do some calc on percentage completed
                ' in the TransferBytes() method of the base class
                SetTransferSize(remoteSize)
            End If

            Dim request As New FtpRequest(MyBase.Encoding, FtpCmd.Retr, remotePath)

            If [resume] Then
                '  make sure we have a valid file size
                If remoteSize = -1 Then
                    Throw New FtpException("unable to determine file size for resume transfer")
                End If

                ' if the files are the same size then there is nothing to transfer
                If outStream.Length = remoteSize Then
                    Return
                End If

                TransferData(TransferDirection.ToClient, request, outStream, outStream.Length - 1)
            Else
                TransferData(TransferDirection.ToClient, request, outStream)
            End If
        End Sub

        ''' <summary>
        ''' Tests to see if a path exists on the remote server.  The current working directory must be the
        ''' parent or root directory of the file or directory whose existence is being tested.  For best results, 
        ''' call this method from the root working directory ("/").
        ''' </summary>
        ''' <param name="path__1">The full path to the remote directory relative to the current working directory you want to test for existence of.</param>
        ''' <return>Boolean value indicating if directory exists or not.</return>
        ''' <remarks>This method will execute a change working directory (CWD) command prior to testing to see if the  
        ''' file exists.  The original working directory will be changed back to the original value
        ''' after this method has completed.  This method may not work on systems where the directory listing is not being
        ''' parsed correctly.  If the method call GetDirList() does not work properly with your FTP server, this method may not
        ''' produce reliable results.  This method will also not produce reliable results if the directory or file is hidden on the
        ''' remote FTP server.</remarks>
        Public Function Exists(path__1 As String) As Boolean
            If [String].IsNullOrEmpty(path__1) Then
                Throw New ArgumentException("must have a value", "path")
            End If

            ' replace the windows style directory delimiter with a unix style delimiter
            path__1 = path__1.Replace("\", "/")
            Dim fname As String = Path.GetFileName(path__1)

            Dim dirs As String() = path__1.Split(New Char() {"/"c}, StringSplitOptions.RemoveEmptyEntries)

            If dirs.Length = 1 Then
                Return Exists("/", fname)
            Else
                Return Exists(path__1.Substring(0, path__1.Length - (fname.Length)), fname)
            End If
        End Function

        ''' <summary>
        ''' Tests to see if a file exists on the remote server.  The current working directory must be the
        ''' parent or root directory of the file or directory whose existence is being tested.  For best results, 
        ''' call this method from the root working directory ("/").
        ''' </summary>
        ''' <param name="path">The full path to the remote directory relative to the current working directory.</param>
        ''' <param name="filename">The name of the file (or directory) test for existence of.</param>
        ''' <return>Boolean value indicating if file exists or not.</return>
        ''' <remarks>This method will execute a change working directory (CWD) command prior to testing to see if the  
        ''' file exists.  The original working directory will be changed back to the original value
        ''' after this method has completed.  This method may not work on systems where the directory listing is not being
        ''' parsed correctly.  If the method call GetDirList() does not work properly with your FTP server, this method may not
        ''' produce reliable results.  This method will also not produce reliable results if the directory or file is hidden on the
        ''' remote FTP server.</remarks>
        Public Function Exists(path As String, filename As String) As Boolean
            If [String].IsNullOrEmpty(path) Then
                Throw New ArgumentException("must have a value", "path")
            End If
            If [String].IsNullOrEmpty(filename) Then
                Throw New ArgumentException("must have a value", "filename")
            End If

            ' replace the windows style directory delimiter with a unix style delimiter
            path = path.Replace("\", "/")

            ' variables for the algorithm
            Dim dirCount As Integer = 0
            ' keep up with the number of valid CHDIR commands issued 
            Dim found As Boolean = False
            ' found flag
            Dim dirs As String() = path.Split(New Char() {"/"c}, StringSplitOptions.RemoveEmptyEntries)

            Try
                ' if the path contains more than just the filename then 
                ' we must change the directory to where the file is located
                If path <> "/" Then
                    For Each dir As String In dirs
                        ChangeDirectory(dir)
                        dirCount += 1
                    Next
                End If

                found = GetDirList().Contains(filename)
                ' surpress ftp CHDIR exceptions
            Catch generatedExceptionName As FtpException
            Finally
                ' always change directory back up to the original directory based on the number
                ' of subdirectories was have gone down into
                ' this is a very reliable method to change directories on all FTP servers
                For j As Integer = 0 To dirCount - 1
                    Me.ChangeDirectoryUp()
                Next
            End Try

            Return found
        End Function


        ''' <summary>
        ''' Retrieves a file name listing of the current working directory from the 
        ''' remote FTP server using the NLST command.
        ''' </summary>
        ''' <return>A string containing the file listing from the current working directory.</return>
        Public Function GetNameList() As String
            Return MyBase.TransferText(New FtpRequest(MyBase.Encoding, FtpCmd.Nlst))
        End Function

        ''' <summary>
        ''' Retrieves a file name listing of the current working directory from the 
        ''' remote FTP server using the NLST command.
        ''' </summary>
        ''' <param name="path">The path to a directory on the remote FTP server.</param>
        ''' <return>A string containing the file listing from the current working directory.</return>
        ''' <remarks>
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the parent directory you wish to get the name list using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        Public Function GetNameList(path As String) As String
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            Return MyBase.TransferText(New FtpRequest(MyBase.Encoding, FtpCmd.Nlst, path))
        End Function

        ''' <summary>
        ''' Retrieves file information for one specific file system object.
        ''' </summary>
        ''' <param name="path">The path to the file system object on the remote FTP server.</param>
        ''' <return>A FtpItem object containing the file system object information.</return>
        ''' <remarks>
        ''' If the FTP features list MLST then the MLST command will be executed and a FtpMlsxItem cast 
        ''' as a FtpItem will be returned.  Otherwise, the standard LIST command will be executed and a
        ''' standard FtpItem object will be returned.
        ''' 
        ''' Note that some FTP servers will not accept a full path to the file system object.  On those systems you must navigate to
        ''' the parent directory which contains the file system object using ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method and invoke the method without any additional path information.
        ''' </remarks>
        ''' <exception cref="FtpFeatureException"></exception>
        Public Function GetFileInfo(path As String) As FtpItem
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            Dim r As FtpRequest = GetFileInfoRequest(path)

            If r.Command = FtpCmd.List Then
                Dim text As String = MyBase.TransferText(r)
                ' if there is not text data then return null
                If [String].IsNullOrEmpty(text) Then
                    Return Nothing
                End If
                Dim lines As String() = text.Split(New Char() {ControlChars.Cr, ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)
                ' if there are are no lines with data then return null
                If lines.Length = 0 Then
                    Return Nothing
                End If
                ' only parse the first line encountered in the event
                ' the caller did not limit the path query correctly
                Return _listItemParser.ParseLine(lines(0))
            Else
                ' FtpCmd.Mlst
                If Not MyBase.Features.Contains(FtpCmd.Mlst) Then
                    Throw New FtpFeatureException("MLST command not a listed supported feature")
                End If
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Mlst, path))
                Dim col As FtpResponseCollection = MyBase.LastResponseList
                If col.Count <> 3 Then
                    Throw New FtpException("incorrect number of response lines received by MLST command text")
                End If
                ' parse the second line and return a mlsx object
                Dim mlsx As FtpItem = _mlsxItemParser.ParseLine(col(1).RawText)


                Return mlsx
            End If
        End Function

        Private Function GetFileInfoRequest(path As String) As FtpRequest
            Select Case _dirListingMethod
                Case ListingMethod.List
                    Return New FtpRequest(MyBase.Encoding, FtpCmd.List, path)
                Case ListingMethod.ListAl
                    Return New FtpRequest(MyBase.Encoding, FtpCmd.List, "-al", path)
                Case ListingMethod.Automatic
                    If Not MyBase.Features.Contains(FtpCmd.Mlst) Then
                        Return New FtpRequest(MyBase.Encoding, FtpCmd.List, "-al", path)
                    Else
                        Return New FtpRequest(MyBase.Encoding, FtpCmd.Mlst, path)
                    End If
                Case ListingMethod.Mlsx
                    Return New FtpRequest(MyBase.Encoding, FtpCmd.Mlst, path)
                Case Else
                    Throw New FtpException("unknown directory listing method")
            End Select
        End Function

        ''' <summary>
        ''' Retrieves a directory listing of the current working directory from the 
        ''' remote FTP server using the LIST command.
        ''' </summary>
        ''' <return>A string containing the directory listing of files from the current working directory.</return>
        Public Function GetDirListAsText() As String
            Return MyBase.TransferText(CreateDirListingRequest())
        End Function

        ''' <summary>
        ''' Retrieves a directory listing of the current working directory from the 
        ''' remote FTP server using the LIST command.
        ''' </summary>
        ''' <param name="path">The path to a directory on the remote FTP server.</param>
        ''' <return>A string containing the directory listing of files from the current working directory.</return>
        ''' <remarks>
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the parent directory you wish to get the name list using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        Public Function GetDirListAsText(path As String) As String
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            Return MyBase.TransferText(CreateDirListingRequest())
        End Function

        ''' <summary>
        ''' Retrieves a list of the files from current working directory on the remote FTP 
        ''' server using the LIST command.  
        ''' </summary>
        ''' <return>FtpItemList collection object.</return>
        ''' <remarks>
        ''' This method return a FtpItemList collection of FtpItem objects.
        ''' </remarks>
        Public Function GetDirList() As FtpItemCollection
            Return New FtpItemCollection(_currentDirectory, MyBase.TransferText(CreateDirListingRequest()), GetItemParser())
        End Function

        ''' <summary>
        ''' Retrieves a list of the files from a specified path on the remote FTP 
        ''' server using the LIST command. 
        ''' </summary>
        ''' <param name="path">The path to a directory on the remote FTP server.</param>
        ''' <return>FtpFileCollection collection object.</return>
        ''' <remarks>
        ''' This method return a FtpFileCollection object containing a collection of 
        ''' FtpItem objects.  Some FTP server implementations will not accept a full path to a resource.  On those
        ''' systems it is best to change the working directory using the ChangeDirectoryMultiPath(string) method and then call
        ''' the method GetDirList().
        ''' </remarks>
        Public Function GetDirList(path As String) As FtpItemCollection
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            Dim _rt As New FTP.FtpItemCollection

            _rt = New FtpItemCollection(path, MyBase.TransferText(CreateDirListingRequest()), GetItemParser())

            Return _rt

        End Function

        ''' <summary>
        ''' Deeply retrieves a list of all files and all sub directories from a specified path on the remote FTP 
        ''' server using the LIST command. 
        ''' </summary>
        ''' <param name="path">The path to a directory on the remote FTP server.</param>
        ''' <return>FtpFileCollection collection object.</return>
        ''' <remarks>
        ''' This method return a FtpFileCollection object containing a collection of 
        ''' FtpItem objects.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the parent directory you wish to get the directory list using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>

        Public Function GetDirListDeep(path As String) As FtpItemCollection
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            Dim deepCol As New FtpItemCollection()
            ParseDirListDeep(path, deepCol)
            Return deepCol
        End Function

        ''' <summary>
        ''' Renames a file or directory on the remote FTP server.
        ''' </summary>
        ''' <param name="name">The name or absolute path of the file or directory you want to rename.</param>
        ''' <param name="newName">The new name or absolute path of the file or directory.</param>
        ''' <seealso cref="SetDateTime"/>
        Public Sub Rename(name As String, newName As String)
            If name Is Nothing Then
                Throw New ArgumentNullException("name", "must have a value")
            End If
            If name.Length = 0 Then
                Throw New ArgumentException("must have a value", "name")
            End If
            If newName Is Nothing Then
                Throw New ArgumentNullException("newName", "must have a value")
            End If
            If newName.Length = 0 Then
                Throw New ArgumentException("must have a value", "newName")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Rnfr, name))
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Rnto, newName))
            Catch fex As FtpException
                Throw New FtpException([String].Format("The FTP destination was unable to rename the file or directory '{0}' to the new name '{1}'.", name, newName), MyBase.LastResponse, fex)
            End Try

        End Sub

        ''' <summary>
        ''' Send a raw FTP command to the server.  Same method as Quote().
        ''' </summary>
        ''' <param name="command">A string containing a valid FTP command value such as SYST.</param>
        ''' <return>The raw textual response from the server.</return>
        ''' <remarks>
        ''' This is an advanced feature of the FtpClient class that allows for any custom or specialized
        ''' FTP command to be sent to the FTP server.  Some FTP server support custom commands outside of
        ''' the standard FTP command list.  The following commands are not supported: PASV, RETR, STOR, STRU, 
        ''' EPRT, and EPSV. The commands LIST, NLST, and MLSD are supported.
        ''' </remarks>
        ''' <example>
        ''' <code>
        ''' FtpClient ftp = new FtpClient("ftp.microsoft.com");
        ''' ftp.Open("anonymous", "myemail@server.com");
        ''' string r = ftp.Quote("SYST");
        ''' System.Diagnostics.Debug.WriteLine(r);
        ''' ftp.Close();
        ''' </code>
        ''' </example>

        Public Function SendCustomCommand(command As String) As String
            Return Quote(command)
        End Function

        ''' <summary>
        ''' Send a raw FTP command to the server.
        ''' </summary>
        ''' <param name="command">A string containing a valid FTP command value such as SYST.</param>
        ''' <return>The raw textual response from the server.</return>
        ''' <remarks>
        ''' This is an advanced feature of the FtpClient class that allows for any custom or specialized
        ''' FTP command to be sent to the FTP server.  Some FTP server support custom commands outside of
        ''' the standard FTP command list.  The following commands are not supported: PASV, RETR, STOR, STRU, 
        ''' EPRT, and EPSV. The commands LIST, NLST, and MLSD are supported.
        ''' </remarks>
        ''' <example>
        ''' <code>
        ''' FtpClient ftp = new FtpClient("ftp.microsoft.com");
        ''' ftp.Open("anonymous", "myemail@server.com");
        ''' string r = ftp.Quote("SYST");
        ''' System.Diagnostics.Debug.WriteLine(r);
        ''' ftp.Close();
        ''' </code>
        ''' </example>
        Public Function Quote(command As String) As String
            If command Is Nothing Then
                Throw New ArgumentNullException("command")
            End If
            If command.Length < 3 Then
                Throw New ArgumentException([String].Format("Invalid command '{0}'.", command), "command")
            End If

            Dim separator As Char() = {" "c}
            Dim values As String() = command.Split(separator)

            ' extract just the code value
            Dim code As String
            If values.Length = 0 Then
                code = command
            Else
                code = values(0)
            End If

            ' extract the arguments
            Dim args As String = String.Empty
            If command.Length > code.Length Then
                args = command.Replace(code, "").TrimStart()
            End If

            Dim ftpCmd__1 As FtpCmd = FtpCmd.Unknown
            Try
                ' try to parse out the command if we can
                ftpCmd__1 = DirectCast([Enum].Parse(GetType(FtpCmd), code, True), FtpCmd)
            Catch
            End Try

            If ftpCmd__1 = FtpCmd.Pasv OrElse ftpCmd__1 = FtpCmd.Retr OrElse ftpCmd__1 = FtpCmd.Stor OrElse ftpCmd__1 = FtpCmd.Stou OrElse ftpCmd__1 = FtpCmd.Eprt OrElse ftpCmd__1 = FtpCmd.Epsv Then
                Throw New ArgumentException([String].Format("Command '{0}' not supported by Quote() method.", code), "command")
            End If

            If ftpCmd__1 = FtpCmd.List OrElse ftpCmd__1 = FtpCmd.Nlst OrElse ftpCmd__1 = FtpCmd.Mlsd Then
                Return MyBase.TransferText(New FtpRequest(MyBase.Encoding, ftpCmd__1, args))
            End If

            If ftpCmd__1 = FtpCmd.Unknown Then
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, ftpCmd__1, command))
            Else
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, ftpCmd__1, args))
            End If

            Return MyBase.LastResponseList.GetRawText()
        End Function



        ''' <summary>
        ''' Sends a NOOP or no operation command to the FTP server.  This can be used to prevent some servers from logging out the
        ''' interactive session during file transfer process.
        ''' </summary>
        Public Sub NoOperation()
            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Noop))
            Catch fex As FtpException
                Throw New FtpException("An error occurred while issuing the No Operation command (NOOP).", MyBase.LastResponse, fex)
            End Try
        End Sub

        ''' <summary>
        ''' Issues a site specific change file mode (CHMOD) command to the server.  Not all servers implement this command.
        ''' </summary>
        ''' <param name="path">The path to the file or directory you want to change the mode on.</param>
        ''' <param name="octalValue">The CHMOD octal value.</param>
        ''' <remarks>
        ''' Common CHMOD values used on web servers.
        ''' 
        '''       Value 	User 	Group 	Other
        '''         755 	rwx 	r-x 	r-x
        '''         744 	rwx 	r--	    r--
        '''         766 	rwx 	rw- 	rw-
        '''         777 	rwx 	rwx 	rwx
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory containing the file or directory you wish to change the mode on by using with the 
        ''' ChangeDirectory() or ChangeDirectoryMultiPath() method.
        ''' </remarks>
        ''' <seealso cref="SetDateTime"/>
        ''' <seealso cref="Rename"/>
        Public Sub ChangeMode(path As String, octalValue As Integer)
            If path Is Nothing Then
                Throw New ArgumentNullException("path")
            End If
            If path.Length = 0 Then
                Throw New ArgumentException("must have a value", "path")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Site, "CHMOD", octalValue.ToString(), path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("Unable to the change file mode for file {0}.  Reason: {1}", path, MyBase.LastResponse.Text), MyBase.LastResponse, fex)
            End Try

            If MyBase.LastResponse.Code = FtpResponseCode.CommandNotImplementedSuperfluousAtThisSite Then
                Throw New FtpException([String].Format("Unable to the change file mode for file {0}.  Reason: {1}", path, MyBase.LastResponse.Text), MyBase.LastResponse)
            End If
        End Sub

        ''' <summary>
        ''' Issue a SITE command to the FTP server for site specific implementation commands.
        ''' </summary>
        ''' <param name="argument">One or more command arguments</param>
        ''' <remarks>
        ''' For example, the CHMOD command is issued as a SITE command.
        ''' </remarks>
        Public Sub Site(argument As String)
            If argument Is Nothing Then
                Throw New ArgumentNullException("argument", "must have a value")
            End If
            If argument.Length = 0 Then
                Throw New ArgumentException("must have a value", "argument")
            End If

            MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Site, argument))
        End Sub

        ''' <summary>
        ''' Uploads a local file specified in the path parameter to the remote FTP server.   
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <param name="remotePath">Filename or full path to file on the remote FTP server.</param>
        ''' <param name="action">The type of put action taken.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server.  The remotePath
        ''' parameter is used to specify the path and file name used to store the file on the remote server.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to put the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>    
        Public Sub PutFile(localPath As String, remotePath As String, action As FileAction)
            Using fileStream As FileStream = File.OpenRead(localPath)
                PutFile(fileStream, remotePath, action)
            End Using
        End Sub

        ''' <summary>
        ''' Uploads a local file specified in the path parameter to the remote FTP server.   
        ''' An FtpException is thrown if the file already exists.
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <param name="remotePath">Filename or full path to file on the remote FTP server.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server.  The remotePath
        ''' parameter is used to specify the path and file name used to store the file on the remote server.
        ''' To overwrite an existing file see the method PutFile(string, string, FileAction) and specify the 
        ''' FileAction Create.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to put the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>            
        Public Sub PutFile(localPath As String, remotePath As String)
            Using fileStream As FileStream = File.OpenRead(localPath)
                PutFile(fileStream, remotePath, FileAction.CreateNew)
            End Using
        End Sub

        ''' <summary>
        ''' Uploads a local file specified in the path parameter to the remote FTP server.   
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <param name="action">The type of put action taken.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server. 
        ''' </remarks>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>            
        Public Sub PutFile(localPath As String, action As FileAction)
            Using fileStream As FileStream = File.OpenRead(localPath)
                PutFile(fileStream, ExtractPathItemName(localPath), action)
            End Using
        End Sub

        ''' <summary>
        ''' Uploads a local file specified in the path parameter to the remote FTP server.   
        ''' An FtpException is thrown if the file already exists.
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server. 
        ''' </remarks>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>    
        Public Sub PutFile(localPath As String)
            Using fileStream As FileStream = File.OpenRead(localPath)
                PutFile(fileStream, ExtractPathItemName(localPath), FileAction.CreateNew)
            End Using
        End Sub

        ''' <summary>
        ''' Uploads stream data specified in the inputStream parameter to the remote FTP server.   
        ''' </summary>
        ''' <param name="inputStream">Any open stream object on the local client machine.</param>
        ''' <param name="remotePath">Filename or path and filename of the file stored on the remote FTP server.</param>
        ''' <param name="action">The type of put action taken.</param>
        ''' <remarks>
        ''' The stream is uploaded to the current working directory on the remote server.  The remotePath
        ''' parameter is used to specify the path and file name used to store the file on the remote server.
        ''' Note that some FTP servers will not accept a full path.  On those systems you must navigate to
        ''' the directory you wish to put the file using with the ChangeDirectory() or ChangeDirectoryMultiPath()
        ''' method.
        ''' </remarks>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>        
        Public Sub PutFile(inputStream As Stream, remotePath As String, action As FileAction)
            If inputStream Is Nothing Then
                Throw New ArgumentNullException("inputStream")
            End If
            If Not inputStream.CanRead Then
                Throw New ArgumentException("must be readable", "inputStream")
            End If
            If remotePath Is Nothing Then
                Throw New ArgumentNullException("remotePath")
            End If
            If remotePath.Length = 0 Then
                Throw New ArgumentException("must contain a value", "remotePath")
            End If
            If action = FileAction.None Then
                Throw New ArgumentOutOfRangeException("action", "must contain a value other than 'Unknown'")
            End If

            ' set the inputStream if the user supplied it pointing to the end of the stream
            If inputStream.CanSeek AndAlso inputStream.Position = inputStream.Length Then
                inputStream.Position = 0
            End If

            WriteToLog([String].Format("Action='PutFile';Status='TransferBegin';RemotePath='{0}';FileAction='{1}'", remotePath, action.ToString()))

            If IsTranferProgressEventSet() AndAlso inputStream.CanSeek Then
                ' set the transfer size so we can do some calc on percentage completed
                ' in the TransferBytes() method of the base class
                SetTransferSize(inputStream.Length)
            End If

            Try
                Select Case action
                    Case FileAction.CreateOrAppend
                        MyBase.TransferData(TransferDirection.ToServer, New FtpRequest(MyBase.Encoding, FtpCmd.Appe, remotePath), inputStream)
                        Exit Select
                    Case FileAction.CreateNew
                        If Exists(remotePath) Then
                            Throw New FtpException("Cannot overwrite existing file when action FileAction.CreateNew is specified.")
                        End If
                        MyBase.TransferData(TransferDirection.ToServer, New FtpRequest(MyBase.Encoding, FtpCmd.Stor, remotePath), inputStream)
                        Exit Select
                    Case FileAction.Create
                        MyBase.TransferData(TransferDirection.ToServer, New FtpRequest(MyBase.Encoding, FtpCmd.Stor, remotePath), inputStream)
                        Exit Select
                    Case FileAction.[Resume]
                        If Not MyBase.Features.Contains(FtpCmd.Rest, "STREAM") Then
                            Throw New FtpCommandNotSupportedException("Cannot resume file transfer.", "REST STREAM")
                        End If

                        '  get the file size for a resume
                        Dim remoteSize As Long
                        If Not TryGetFileSize(remotePath, remoteSize) Then
                            Throw New FtpException("unable to determine file size for resume transfer")
                        End If

                        '  if the files are the same size then there is nothing to transfer
                        If remoteSize = inputStream.Length Then
                            Return
                        End If

                        '  transfer file to the server
                        MyBase.TransferData(TransferDirection.ToServer, New FtpRequest(MyBase.Encoding, FtpCmd.Stor, remotePath), inputStream, remoteSize)
                        Exit Select
                    Case FileAction.ResumeOrCreate
                        ' if the remote file exists then do a resume otherwise do a create
                        If Exists(remotePath) Then
                            PutFile(inputStream, remotePath, FileAction.[Resume])
                        Else
                            PutFile(inputStream, remotePath, FileAction.Create)
                        End If
                        Exit Select
                End Select
            Catch fex As FtpException
                WriteToLog([String].Format("Action='PutFile';Status='TransferError';RemotePath='{0}';FileAction='{1}';ErrorMessage='{2}'", remotePath, action.ToString(), fex.Message))
                Throw New FtpDataTransferException([String].Format("An error occurred while putting fileName '{0}'.", remotePath), MyBase.LastResponse, fex)
            End Try

            WriteToLog([String].Format("Action='PutFile';Status='TransferSuccess';RemotePath='{0}';FileAction='{1}'", remotePath, action.ToString()))
        End Sub

        ''' <summary>
        ''' File Exchange Protocol (FXP) allows server-to-server transfer which can greatly speed up file transfers.
        ''' </summary>
        ''' <param name="fileName">The name of the file to transfer.</param>
        ''' <param name="destination">The destination FTP server which must be supplied as an open and connected FtpClient object.</param>
        ''' <remarks>
        ''' Both servers must support and have FXP enabled before you can transfer files between two remote servers using FXP.  One FTP server must support PASV mode and the other server must allow PORT commands from a foreign address.  Finally, firewall settings may interfer with the ability of one server to access the other server.
        ''' Starksoft FtpClient will coordinate the FTP negoitaion and necessary PORT and PASV transfer commands.
        ''' </remarks>
        ''' <seealso cref="FxpTransferTimeout"/>
        ''' <seealso cref="FxpCopyAsync"/> 
        Public Sub FxpCopy(fileName As String, destination As FtpClient)
            If Me.IsConnected = False Then
                Throw New FtpException("The connection must be open before a transfer between servers can be intitiated.")
            End If
            If destination Is Nothing Then
                Throw New ArgumentNullException("destination")
            End If
            If destination.IsConnected = False Then
                Throw New FtpException("The destination object must be open and connected before a transfer between servers can be intitiated.")
            End If
            If [String].IsNullOrEmpty(fileName) Then
                Throw New ArgumentException("must have a value", "fileName")
            End If

            '  send command to destination FTP server to get passive port to be used from the source FTP server
            Try
                destination.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Pasv))
            Catch fex As FtpException
                Throw New FtpException([String].Format("An error occurred when trying to set up the passive connection on '{1}' for a destination to destination copy between '{0}' and '{1}'.", Me.Host, destination.Host), MyBase.LastResponse, fex)
            End Try

            '  get the begin and end positions to extract data from the response string
            Dim startIdx As Integer = destination.LastResponse.Text.IndexOf("(") + 1
            Dim endIdx As Integer = destination.LastResponse.Text.IndexOf(")")
            Dim dataPortInfo As String = destination.LastResponse.Text.Substring(startIdx, endIdx - startIdx)

            '  send a command to the source server instructing it to connect to
            '  the local ip address and port that the destination server will be bound to
            Try
                Me.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Port, dataPortInfo))
            Catch fex As FtpException
                Throw New FtpException([String].Format("Command instructing '{0}' to open connection failed.", Me.Host), MyBase.LastResponse, fex)
            End Try

            ' send command to tell the source server to retrieve the file from the destination server
            Try
                Me.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Retr, fileName))
            Catch fex As FtpException
                Throw New FtpException([String].Format("An error occurred transfering on a server to server copy between '{0}' and '{1}'.", Me.Host, destination.Host), MyBase.LastResponse, fex)
            End Try

            ' send command to tell the destination to store the file
            Try
                destination.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Stor, fileName))
            Catch fex As FtpException
                Throw New FtpException([String].Format("An error occurred transfering on a server to server copy between '{0}' and '{1}'.", Me.Host, destination.Host), MyBase.LastResponse, fex)
            End Try

            ' wait until we get a file completed response back from the destination server and the source server
            destination.WaitForHappyCodes(Me.FxpTransferTimeout, FtpResponseCode.RequestedFileActionOkayAndCompleted, FtpResponseCode.ClosingDataConnection)
            Me.WaitForHappyCodes(Me.FxpTransferTimeout, FtpResponseCode.RequestedFileActionOkayAndCompleted, FtpResponseCode.ClosingDataConnection)
        End Sub

        ''' <summary>
        ''' Instructs the FTP server to try to set the server to UTF-8 encoding on.  ASCII encoding is assumed on a failure.
        ''' </summary>
        ''' <remarks>
        ''' This method will attempt to execute the OPTS UTF8 ON command if UTF8 is a supported feature and OPTS is a supported command.
        ''' FileZilla and UTF-8 Info: http://wiki.filezilla-project.org/Character_Set
        ''' RFC 2640 - UTF8 options: http://tools.ietf.org/html/rfc2640        
        ''' </remarks>
        ''' <seealso cref="SetUtf8On"/>
        ''' <seealso cref="SetUtf8Off"/>
        ''' <return>True if UTF-8 encoding is set; otherwise false.</return>
        Public Function TrySetUtf8On() As Boolean
            Try
                SetUtf8On()
                Return True
            Catch generatedExceptionName As FtpException
                ' assume ASCII encoding on failure.
                MyBase.Encoding = Encoding.Default
            End Try



            Return False
        End Function

        ''' <summary>
        ''' Instructs the FTP server to set the server to UTF-8 encoding on.
        ''' </summary>
        ''' <remarks>
        ''' This method will attempt to execute the OPTS UTF8 ON command if UTF8 is a supported feature and OPTS is a supported command.
        ''' FileZilla and UTF-8 Info: http://wiki.filezilla-project.org/Character_Set
        ''' RFC 2640 - UTF8 options: http://tools.ietf.org/html/rfc2640        
        ''' </remarks>
        ''' <exception cref="FtpFeatureException">If UTF-8 encoding is not a supported feature, a FtpFeatureException will be thrown.</exception>
        ''' <exception cref="FtpCommandNotSupportedException">If OPTS is not a supported command then a FtpCommandNotSupportedException will be thrown.</exception>
        ''' <seealso cref="SetUtf8Off"/>
        ''' <seealso cref="TrySetUtf8On"/>
        Public Sub SetUtf8On()
            ' test to see if UTF8 is supported by the server in the features list
            If MyBase.Features.Contains("UTF8") Then
                SetOptions("UTF8 ON")
                MyBase.Encoding = Encoding.UTF8
            Else
                Throw New FtpFeatureException("The feature option UTF8 is not supported by the FTP server.")
            End If
        End Sub

        ''' <summary>
        ''' Instructs the FTP server to set the server to UTF-8 encoding off.
        ''' </summary>
        ''' <remarks>
        ''' This method will attempt to execute the OPTS UTF8 OFF command if UTF8 is a supported feature and OPTS is a supported command.
        ''' FileZilla and UTF-8 Info: http://wiki.filezilla-project.org/Character_Set
        ''' RFC 2640 - UTF8 options: http://tools.ietf.org/html/rfc2640        
        ''' </remarks>
        ''' <exception cref="FtpFeatureException">If UTF-8 encoding is not a supported feature, a FtpFeatureException will be thrown.</exception>
        ''' <exception cref="FtpCommandNotSupportedException">If OPTS is not a supported command then a FtpCommandNotSupportedException will be thrown.</exception>
        ''' <seealso cref="SetUtf8On"/>
        ''' <seealso cref="TrySetUtf8On"/>
        Public Sub SetUtf8Off()
            ' test to see if UTF8 is supported by the server in the features list
            If MyBase.Features.Contains("UTF8") Then
                SetOptions("UTF8 OFF")
                MyBase.Encoding = Encoding.ASCII
            Else
                Throw New FtpFeatureException("The feature option UTF8 is not supported by the FTP server.")
            End If
        End Sub


        ''' <summary>
        ''' Executes the OPTS (Options) command on the FTP server.
        ''' </summary>
        ''' <remarks>
        ''' Every FTP server supports a different set of options that can be set and specified
        ''' to turn on or off specific features on the FTP server.  Use this command to set 
        ''' specific options on or off.
        ''' Example:
        '''     Options("UTF8 OFF");
        ''' </remarks>
        ''' <param name="parameters">Paramater text to execute with the OPTS command.</param>
        ''' <seealso cref="SetUtf8On"/>
        ''' <seealso cref="SetUtf8Off"/>
        ''' <seealso cref="TrySetUtf8On"/>
        Public Sub SetOptions(parameters As String)
            If [String].IsNullOrEmpty(parameters) Then
                Throw New ArgumentException("must have a value", "fileName")
            End If

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Opts, parameters))
            Catch fex As FtpException
                Throw New FtpException("An error occurred while executing the OPTS command.", MyBase.LastResponse, fex)
            End Try

        End Sub

#End Region

#Region "Private Methods"

        ''' <summary>
        ''' Attempt to send the CLieNT command to identify the FTP client.  Some FTP
        ''' servers require this command to function properly and many other ignore it
        ''' or simply do not implement the command.  Errors are ignored.
        ''' </summary>
        Private Sub TrySetClient()
            Try
                If MyBase.Features.Contains(FtpCmd.Clnt) Then
                    SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Clnt, FTP_CLIENT_NAME))
                End If
                ' ignore errors
            Catch generatedExceptionName As FtpException
            End Try
        End Sub


        ''' <summary>
        ''' Attempt to set the file transfer type.  If unsuccesful ignore errors
        ''' and set the FileTransferType to 'Unknown'.
        ''' </summary>
        Private Sub TrySetFileTransferType()
            Try
                SetFileTransferType(_fileTransferType)
            Catch generatedExceptionName As FtpException
                ' on error assume Unknown transfer type
                _fileTransferType = TransferType.Unknown
            End Try
        End Sub

        ''' <summary>
        ''' Attempt to enable compress if that option has been selected.  If the operation fails
        ''' an exception is thrown and the connection is terminated.
        ''' </summary>
        Private Sub SetCompression()
            If MyBase.IsCompressionEnabled Then
                MyBase.CompressionOn()
            End If
        End Sub

        Private Sub CreateItemParsers()
            ' if the custom item parser is not set then set to use the built-in generic parser
            If _listItemParser Is Nothing Then
                _listItemParser = New FtpListItemParser
            End If
            ' create the MLSx item parser
            _mlsxItemParser = New FtpListItemParser
        End Sub

        ' send command to set the file transfer type on the FTP server
        Private Sub SetFileTransferType(t As TransferType)
            Select Case t
                Case TransferType.Binary
                    MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Type, TYPE_BINARY_I))
                    Exit Select
                Case TransferType.Ascii
                    MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Type, TYPE_ASCII_A))
                    Exit Select
            End Select
        End Sub

        Private Sub SendPassword(password As String)
            ' some ftp servers do not require passwords for users and will log you in immediately - no password command is required
            If MyBase.LastResponse.Code <> FtpResponseCode.UserLoggedIn Then
                Try
                    ' send the password request as ASCII encoded text always                    
                    MyBase.SendRequest(New FtpRequest(Encoding.ASCII, FtpCmd.Pass, password))
                Catch fex As FtpException
                    Throw New FtpConnectionOpenException([String].Format("An error occurred when sending password information.  Reason: {0}", MyBase.LastResponse.Text), fex)
                End Try

                If MyBase.LastResponse.Code = FtpResponseCode.NotLoggedIn Then
                    Throw New FtpLoginException("Unable to log into FTP destination with supplied username and password.")
                End If
            End If
        End Sub

        Private Sub SendUser(user As String)
            Try
                ' send the username request as ASCII encoded text always
                MyBase.SendRequest(New FtpRequest(Encoding.ASCII, FtpCmd.User, user))
            Catch fex As FtpException
                Throw New FtpConnectionOpenException([String].Format("An error occurred when sending user information.  Reason: {0}", MyBase.LastResponse.Text), fex)
            End Try
        End Sub

        Private Function CatchUserCancel() As Boolean
            ' test to see if this is an asychronous operation and if so make sure 
            ' the user has not requested the operation to be canceled
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.CancellationPending Then
                MyBase.CloseAllConnections()
                Return True
            End If
            Return False
        End Function

        Private Sub ParseDirListDeep(path As String, deepCol As FtpItemCollection)

            MyBase.SendRequest(New FtpRequest(MyBase.Encoding, FtpCmd.Cwd, path))

            _currentDirectory = path

            Dim list As FtpItemCollection = GetDirList()
            deepCol.Merge(list)

            For Each item As FtpItem In list
                ' if the this call is being completed asynchronously and the user requests a cancellation
                ' then stop processing the items and return
                If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.CancellationPending Then
                    Return
                End If

                ' if the item is of type Directory then parse the directory list recursively
                If item.ItemType = FtpItemType.Directory Then
                    ParseDirListDeep(item.FullPath, deepCol)
                End If
            Next
        End Sub

        Private Function CorrectLocalPath(path__1 As String) As String
            If path__1 Is Nothing Then
                Throw New ArgumentNullException("path")
            End If

            If path__1.Length = 0 Then
                Throw New ArgumentException("must have a value", "path")
            End If

            Dim fileName As String = ExtractPathItemName(path__1)
            Dim pathOnly As String = path__1.Substring(0, path__1.Length - fileName.Length - 1)

            ' if the pathOnly portion contains the root node then we need to add the 
            ' a directory slash back otherwise the final combined path will be something
            ' like c:myfile.txt and this will result
            If pathOnly.EndsWith(":") AndAlso pathOnly.IndexOf("\") = -1 Then
                pathOnly += "\"
            End If

            Dim invalidPath As Char() = Path.GetInvalidPathChars()
            If path__1.IndexOfAny(invalidPath) <> -1 Then
                For i As Integer = 0 To invalidPath.Length - 1
                    If pathOnly.IndexOf(invalidPath(i)) <> -1 Then
                        pathOnly = pathOnly.Replace(invalidPath(i), "_"c)
                    End If
                Next
            End If

            Dim invalidFile As Char() = Path.GetInvalidFileNameChars()
            If fileName.IndexOfAny(invalidFile) <> -1 Then
                For i As Integer = 0 To invalidFile.Length - 1
                    If fileName.IndexOf(invalidFile(i)) <> -1 Then
                        fileName = fileName.Replace(invalidFile(i), "_"c)
                    End If
                Next
            End If

            Return Path.Combine(pathOnly, fileName)

        End Function

        Private Function ExtractPathItemName(path As String) As String
            If path.IndexOf("\") <> -1 Then
                Return path.Substring(path.LastIndexOf("\") + 1)
            ElseIf path.IndexOf("/") <> -1 Then
                Return path.Substring(path.LastIndexOf("/") + 1)
            ElseIf path.Length > 0 Then
                Return path
            Else
                Throw New FtpException([String].Format(CultureInfo.InvariantCulture, "Item name not found in path {0}.", path))
            End If
        End Function


        Private Sub WriteToLog(message As String)
            If Not _isLoggingOn Then
                Return
            End If

            Dim line As String = [String].Format("[{0}] [{1}] [{2}] {3}" & vbCr & vbLf, DateTime.Now.ToString("G"), MyBase.Host, MyBase.Port.ToString(), message)
            Dim buffer As Byte() = MyBase.Encoding.GetBytes(line)
            _log.Write(buffer, 0, buffer.Length)
        End Sub

        ''' <summary>
        ''' Set the date and time for a specific file or directory on the server using a specific FTP command.
        ''' </summary>
        ''' <param name="path">The path or name of the file or directory.</param>
        ''' <param name="dateTime">New date to set on the file or directory.</param>
        ''' <param name="cmd">MFCT or MFMT command to use</param>
        Private Sub SetDateTime(path As String, dateTime As DateTime, cmd As FtpCmd)
            If [String].IsNullOrEmpty(path) Then
                Throw New ArgumentException("must have a value", "path")
            End If

            If cmd <> FtpCmd.Mfmt AndAlso cmd <> FtpCmd.Mfct Then
                Throw New ArgumentOutOfRangeException("cmd", "only Mfmt and Mfct options are supported")
            End If

            If Not MyBase.Features.Contains(cmd) Then
                Throw New FtpCommandNotSupportedException("Cannot set date time of file.", cmd)
            End If

            ' replay format: MFMT [YYMMDDHHMMSS] [filename]

            Dim dateTimeArg As String = dateTime.ToString("yyyyMMddHHmmss")

            Try
                MyBase.SendRequest(New FtpRequest(MyBase.Encoding, cmd, dateTimeArg, path))
            Catch fex As FtpException
                Throw New FtpException([String].Format("An error occurred when setting a file date and time for '{0}'.", path), fex)
            End Try
        End Sub

        Private Function CreateDirListingRequest() As FtpRequest
            Select Case _dirListingMethod
                Case ListingMethod.List
                    Return New FtpRequest(MyBase.Encoding, FtpCmd.List)
                Case ListingMethod.ListAl
                    Return New FtpRequest(MyBase.Encoding, FtpCmd.List, "-al")
                Case ListingMethod.Mlsx
                    Return New FtpRequest(MyBase.Encoding, FtpCmd.Mlsd)
                Case ListingMethod.Automatic
                    If MyBase.Features.Contains(FtpCmd.Mlsd) OrElse MyBase.Features.Contains(FtpCmd.Mlst) Then
                        Return New FtpRequest(MyBase.Encoding, FtpCmd.Mlsd)
                    Else
                        Return New FtpRequest(MyBase.Encoding, FtpCmd.List)
                    End If
                Case Else
                    Throw New FtpException("unknown directory listing option")
            End Select
        End Function

        Private Function GetItemParser() As IFtpItemParser
            Select Case _dirListingMethod
                Case ListingMethod.List, ListingMethod.ListAl
                    Return _listItemParser
                Case ListingMethod.Mlsx
                    Return _mlsxItemParser
                Case ListingMethod.Automatic
                    If MyBase.Features.Contains(FtpCmd.Mlsd) OrElse MyBase.Features.Contains(FtpCmd.Mlst) Then
                        Return _mlsxItemParser
                    Else
                        Return _listItemParser
                    End If
                Case Else
                    Throw New FtpException("unknown directory listing option")
            End Select
        End Function


#End Region

#Region "Asynchronous Methods and Events"

        Private _asyncException As Exception


        ''' <summary>
        ''' Event handler for GetDirListAsync method.
        ''' </summary>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        Public Event GetDirListAsyncCompleted As EventHandler(Of GetDirListAsyncCompletedEventArgs)

        ''' <summary>
        ''' Asynchronously retrieves a list of the files from current working directory on the remote FTP 
        ''' server using the LIST command.  
        ''' </summary>
        ''' <remarks>
        ''' This method return a FtpItemList collection of FtpItem objects through the GetDirListAsyncCompleted event.
        ''' </remarks>
        ''' <seealso cref="GetDirListAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        Public Sub GetDirListAsync()
            GetDirListAsync(String.Empty)
        End Sub

        ''' <summary>
        ''' Asynchronously retrieves a list of the files from a specified path on the remote FTP 
        ''' server using the LIST command. 
        ''' </summary>
        ''' <param name="path">The path to a directory on the remote FTP server.</param>
        ''' <remarks>This method return a FtpFileCollection object containing a collection of 
        ''' FtpItem objects.  The FtpFileCollection is returned though the GetDirListAsyncCompleted event.</remarks>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        Public Sub GetDirListAsync(path As String)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf GetDirListAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf GetDirListAsync_RunWorkerCompleted
            MyBase.AsyncWorker.RunWorkerAsync(path)
        End Sub

        Private Sub GetDirListAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim path As String = DirectCast(e.Argument, String)
                e.Result = GetDirList(path)
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        Private Sub GetDirListAsync_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            RaiseEvent GetDirListAsyncCompleted(Me, New GetDirListAsyncCompletedEventArgs(_asyncException, MyBase.IsAsyncCanceled, DirectCast(e.Result, FtpItemCollection)))
            _asyncException = Nothing
        End Sub

        ''' <summary>
        ''' Event handler for GetDirListDeepAsync method.
        ''' </summary>
        Public Event GetDirListDeepAsyncCompleted As EventHandler(Of GetDirListDeepAsyncCompletedEventArgs)

        ''' <summary>
        ''' Asynchronous deep retrieval of a list of all files and all sub directories from the current path on the remote FTP 
        ''' server using the LIST command. 
        ''' </summary>
        ''' <remarks>This method return a FtpFileCollection object containing a collection of FtpItem objects through the GetDirListDeepAsyncCompleted event.</remarks>
        ''' <seealso cref="GetDirListDeepAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        Public Sub GetDirListDeepAsync()
            GetDirListDeepAsync(GetWorkingDirectory())
        End Sub

        ''' <summary>
        ''' Asynchronous deep retrieval of a list of all files and all sub directories from a specified path on the remote FTP 
        ''' server using the LIST command. 
        ''' </summary>
        ''' <param name="path">The path to a directory on the remote FTP server.</param>
        ''' <remarks>This method return a FtpFileCollection object containing a collection of 
        ''' FtpItem objects the GetDirListDeepAsyncCompleted event.</remarks>
        ''' <seealso cref="GetDirListDeepAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        Public Sub GetDirListDeepAsync(path As String)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf GetDirListDeepAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf GetDirListDeepAsync_RunWorkerCompleted
            MyBase.AsyncWorker.RunWorkerAsync(path)
        End Sub

        Private Sub GetDirListDeepAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim path As String = DirectCast(e.Argument, String)
                e.Result = GetDirList(path)
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        Private Sub GetDirListDeepAsync_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            RaiseEvent GetDirListDeepAsyncCompleted(Me, New GetDirListDeepAsyncCompletedEventArgs(_asyncException, MyBase.IsAsyncCanceled, DirectCast(e.Result, FtpItemCollection)))
            _asyncException = Nothing
        End Sub

        ''' <summary>
        ''' Event that fires when the GetFileAsync method is invoked.
        ''' </summary>
        Public Event GetFileAsyncCompleted As EventHandler(Of GetFileAsyncCompletedEventArgs)

        ''' <summary>
        ''' Asynchronously retrieves a remote file from the FTP server and writes the data to a local file
        ''' specfied in the localPath parameter.
        ''' </summary>
        ''' <param name="remotePath">A path and/or file name to the remote file.</param>
        ''' <param name="localPath">A fully qualified local path to a file on the local machine.</param>
        ''' <param name="action">The type of action to take.</param>
        ''' <seealso cref="GetFileAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>
        Public Sub GetFileAsync(remotePath As String, localPath As String, action As FileAction)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf GetFileAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf GetFileAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](2) {}
            args(0) = remotePath
            args(1) = localPath
            args(2) = action
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub GetFileAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                GetFile(DirectCast(args(0), String), DirectCast(args(1), String), CType(args(2), FileAction))
            Catch ex As Exception
                _asyncException = ex
            End Try

        End Sub

        Private Sub GetFileAsync_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            RaiseEvent GetFileAsyncCompleted(Me, New GetFileAsyncCompletedEventArgs(_asyncException, MyBase.IsAsyncCanceled))
            _asyncException = Nothing
        End Sub

        ''' <summary>
        ''' Asynchronously retrieves a remote file from the FTP server and writes the data to a local stream object
        ''' specfied in the outStream parameter.
        ''' </summary> 
        ''' <param name="remotePath">A path and/or file name to the remote file.</param>
        ''' <param name="outStream">An output stream object used to stream the remote file to the local machine.</param>
        ''' <param name="restart">A true/false value to indicate if the file download needs to be restarted due to a previous partial download.</param>
        ''' <remarks>
        ''' If the remote path is a file name then the file is downloaded from the FTP server current working directory.  Otherwise a fully qualified
        ''' path for the remote file may be specified.  The output stream must be writeable and can be any stream object.  Finally, the restart parameter
        ''' is used to send a restart command to the FTP server.  The FTP server is instructed to restart the download process at the last position of
        ''' of the output stream.  Not all FTP servers support the restart command.  If the FTP server does not support the restart (REST) command,
        ''' an FtpException error is thrown.
        ''' </remarks>        
        ''' <seealso cref="GetFileAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>
        Public Sub GetFileAsync(remotePath As String, outStream As Stream, restart As Boolean)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf GetFileStreamAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf GetFileAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](2) {}
            args(0) = remotePath
            args(1) = outStream
            args(2) = restart
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub GetFileStreamAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                GetFile(DirectCast(args(0), String), DirectCast(args(1), Stream), CBool(args(2)))
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        ''' <summary>
        ''' Asynchronous event for PutFileAsync method.
        ''' </summary>
        Public Event PutFileAsyncCompleted As EventHandler(Of PutFileAsyncCompletedEventArgs)

        ''' <summary>
        ''' Asynchronously uploads a local file specified in the path parameter to the remote FTP server.   
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <param name="remotePath">Filename or full path to file on the remote FTP server.</param>
        ''' <param name="action">The type of put action taken.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server.  The remotePath
        ''' parameter is used to specify the path and file name used to store the file on the remote server.
        ''' </remarks>
        ''' <seealso cref="PutFileAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>  
        Public Sub PutFileAsync(localPath As String, remotePath As String, action As FileAction)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf PutFileAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf PutFileAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](2) {}
            args(0) = localPath
            args(1) = remotePath
            args(2) = action
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub PutFileAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                PutFile(DirectCast(args(0), String), DirectCast(args(1), String), CType(args(2), FileAction))
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        Private Sub PutFileAsync_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            RaiseEvent PutFileAsyncCompleted(Me, New PutFileAsyncCompletedEventArgs(_asyncException, MyBase.IsAsyncCanceled))
            _asyncException = Nothing
        End Sub

        ''' <summary>
        ''' Asynchronously uploads stream data specified in the inputStream parameter to the remote FTP server.   
        ''' </summary>
        ''' <param name="inputStream">Any open stream object on the local client machine.</param>
        ''' <param name="remotePath">Filename or path and filename of the file stored on the remote FTP server.</param>
        ''' <param name="action">The type of put action taken.</param>
        ''' <remarks>
        ''' The stream is uploaded to the current working directory on the remote server.  The remotePath
        ''' parameter is used to specify the path and file name used to store the file on the remote server.
        ''' </remarks>
        ''' <seealso cref="PutFileAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>    
        Public Sub PutFileAsync(inputStream As Stream, remotePath As String, action As FileAction)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf PutFileStreamAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf PutFileAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](2) {}
            args(0) = inputStream
            args(1) = remotePath
            args(2) = action
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub PutFileStreamAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                PutFile(DirectCast(args(0), Stream), DirectCast(args(1), String), CType(args(2), FileAction))
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        ''' <summary>
        ''' Asynchronously uploads a local file specified in the path parameter to the remote FTP server.   
        ''' </summary>
        ''' <param name="localPath">Path to a file on the local machine.</param>
        ''' <param name="action">The type of put action taken.</param>
        ''' <remarks>
        ''' The file is uploaded to the current working directory on the remote server. 
        ''' </remarks>
        ''' <seealso cref="PutFileAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        ''' <seealso cref="MoveFile"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FxpCopyAsync"/>    
        Public Sub PutFileAsync(localPath As String, action As FileAction)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf PutFileLocalAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf PutFileAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](1) {}
            args(0) = localPath
            args(1) = action
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub PutFileLocalAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                PutFile(DirectCast(args(0), String), CType(args(1), FileAction))
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub


        ''' <summary>
        ''' Event handler for OpenAsync method.
        ''' </summary>
        Public Event OpenAsyncCompleted As EventHandler(Of OpenAsyncCompletedEventArgs)

        ''' <summary>
        ''' Asynchronously opens a connection to the remote FTP server and log in with user name and password credentials.
        ''' </summary>
        ''' <param name="user">User name.  Many public ftp allow for this value to 'anonymous'.</param>
        ''' <param name="password">Password.  Anonymous public ftp servers generally require a valid email address for a password.</param>
        ''' <remarks>Use the Close() method to log off and close the connection to the FTP server.</remarks>
        ''' <seealso cref="OpenAsyncCompleted"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        ''' <seealso cref="Open"/>
        ''' <seealso cref="Reopen"/>
        ''' <seealso cref="Close"/>
        Public Sub OpenAsync(user As String, password As String)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf OpenAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf OpenAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](1) {}
            args(0) = user
            args(1) = password
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub OpenAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                Open(DirectCast(args(0), String), DirectCast(args(1), String))
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        Private Sub OpenAsync_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            RaiseEvent OpenAsyncCompleted(Me, New OpenAsyncCompletedEventArgs(_asyncException, MyBase.IsAsyncCanceled))
            _asyncException = Nothing
        End Sub



        ''' <summary>
        ''' Asynchronous event for FxpCopyAsync method.
        ''' </summary>
        Public Event FxpCopyAsyncCompleted As EventHandler(Of FxpCopyAsyncCompletedEventArgs)

        ''' <summary>
        ''' Asynchronous File Exchange Protocol (FXP) allows server-to-server transfer which can greatly speed up file transfers.
        ''' </summary>
        ''' <param name="fileName">The name of the file to transfer.</param>
        ''' <param name="destination">The destination FTP server which must be supplied as an open and connected FtpClient object.</param>
        ''' <remarks>
        ''' Both servers must support and have FXP enabled before you can transfer files between two remote servers using FXP.  One FTP server must support PASV mode and the other server must allow PORT commands from a foreign address.  Finally, firewall settings may interfer with the ability of one server to access the other server.
        ''' Starksoft FtpClient will coordinate the FTP negoitaion and necessary PORT and PASV transfer commands.
        ''' </remarks>
        ''' <seealso cref="FxpCopyAsyncCompleted"/>
        ''' <seealso cref="FxpTransferTimeout"/>
        ''' <seealso cref="FxpCopy"/>
        ''' <seealso cref="FtpBase.CancelAsync"/>
        Public Sub FxpCopyAsync(fileName As String, destination As FtpClient)
            If MyBase.AsyncWorker IsNot Nothing AndAlso MyBase.AsyncWorker.IsBusy Then
                Throw New InvalidOperationException("The FtpClient object is already busy executing another asynchronous operation.  You can only execute one asychronous method at a time.")
            End If

            MyBase.CreateAsyncWorker()
            MyBase.AsyncWorker.WorkerSupportsCancellation = True
            AddHandler MyBase.AsyncWorker.DoWork, AddressOf FxpCopyAsync_DoWork
            AddHandler MyBase.AsyncWorker.RunWorkerCompleted, AddressOf FxpCopyAsync_RunWorkerCompleted
            Dim args As [Object]() = New [Object](1) {}
            args(0) = fileName
            args(1) = destination
            MyBase.AsyncWorker.RunWorkerAsync(args)
        End Sub

        Private Sub FxpCopyAsync_DoWork(sender As Object, e As DoWorkEventArgs)
            Try
                Dim args As [Object]() = DirectCast(e.Argument, [Object]())
                FxpCopy(DirectCast(args(0), String), DirectCast(args(1), FtpClient))
            Catch ex As Exception
                _asyncException = ex
            End Try
        End Sub

        Private Sub FxpCopyAsync_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
            RaiseEvent FxpCopyAsyncCompleted(Me, New FxpCopyAsyncCompletedEventArgs(_asyncException, MyBase.IsAsyncCanceled))
            _asyncException = Nothing
        End Sub

#End Region

#Region "Destructors"

        ''' <summary>
        ''' Disposes all FtpClient objects and connections.
        ''' </summary>
        Protected Overridable Shadows Sub Dispose()
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub

        ''' <summary>
        ''' Dispose Method.
        ''' </summary>
        ''' <param name="disposing"></param>
        Protected Shadows Sub Dispose(disposing As Boolean)
            ' close any managed objects
            If disposing Then
            End If

            MyBase.Dispose(disposing)
        End Sub

        ''' <summary>
        ''' Dispose deconstructor.
        ''' </summary>
        Protected Overrides Sub Finalize()
            Try
                Dispose(False)
            Finally
                MyBase.Finalize()
            End Try
        End Sub

#End Region
    End Class
End Namespace




